From c52c952f0a25ebdb2ea5e22699e5e8fb64e559d4 Mon Sep 17 00:00:00 2001 From: Julien Garcia Gonzalez Date: Mon, 27 Feb 2017 15:02:08 +0100 Subject: [PATCH] add clairctl --- cmd/clairctl/.gitignore | 3 + cmd/clairctl/.travis.yml | 25 + cmd/clairctl/LICENSE | 202 +++ cmd/clairctl/README.md | 58 + cmd/clairctl/VERSION | 1 + cmd/clairctl/clair/analyze.go | 73 + cmd/clairctl/clair/clair.go | 63 + cmd/clairctl/clair/clair_test.go | 65 + cmd/clairctl/clair/health.go | 20 + cmd/clairctl/clair/layering.go | 102 ++ cmd/clairctl/clair/push.go | 108 ++ cmd/clairctl/clair/push_test.go | 28 + cmd/clairctl/clair/report.go | 141 ++ cmd/clairctl/clair/report_test.go | 39 + cmd/clairctl/clair/samples/clair_report.json | 1558 +++++++++++++++++ cmd/clairctl/clair/templates.go | 237 +++ .../clair/templates/analysis-template.html | 558 ++++++ cmd/clairctl/clair/versions.go | 23 + cmd/clairctl/clairctl.yml.default | 10 + cmd/clairctl/cmd/analyze.go | 60 + cmd/clairctl/cmd/health.go | 37 + cmd/clairctl/cmd/pull.go | 65 + cmd/clairctl/cmd/push.go | 58 + cmd/clairctl/cmd/report.go | 99 ++ cmd/clairctl/cmd/root.go | 54 + cmd/clairctl/cmd/version.go | 35 + cmd/clairctl/config/config.go | 342 ++++ cmd/clairctl/config/config_test.go | 121 ++ cmd/clairctl/contrib/.hyperclair.yml | 7 + cmd/clairctl/contrib/README.md | 14 + .../auth_server/config/auth_config.yml | 29 + .../contrib/auth_server/ssl/old/server.key | 28 + .../contrib/auth_server/ssl/old/server.pem | 21 + .../contrib/auth_server/ssl/server.key | 28 + .../contrib/auth_server/ssl/server.pem | 21 + cmd/clairctl/contrib/config/clair.yml | 77 + cmd/clairctl/contrib/docker-compose.yml | 52 + .../contrib/ssl_registry/registry.crt | 34 + .../contrib/ssl_registry/registry.key | 51 + cmd/clairctl/docker/docker.go | 54 + cmd/clairctl/docker/dockercli/dockercli.go | 224 +++ cmd/clairctl/docker/dockerdist/auth.go | 83 + cmd/clairctl/docker/dockerdist/dockerdist.go | 271 +++ cmd/clairctl/main.go | 21 + cmd/clairctl/server/server.go | 120 ++ cmd/clairctl/test/test.go | 39 + cmd/clairctl/xstrings/xstrings.go | 29 + cmd/clairctl/xstrings/xstrings_test.go | 27 + glide.yaml | 29 + 49 files changed, 5444 insertions(+) create mode 100644 cmd/clairctl/.gitignore create mode 100644 cmd/clairctl/.travis.yml create mode 100644 cmd/clairctl/LICENSE create mode 100644 cmd/clairctl/README.md create mode 100644 cmd/clairctl/VERSION create mode 100644 cmd/clairctl/clair/analyze.go create mode 100644 cmd/clairctl/clair/clair.go create mode 100644 cmd/clairctl/clair/clair_test.go create mode 100644 cmd/clairctl/clair/health.go create mode 100644 cmd/clairctl/clair/layering.go create mode 100644 cmd/clairctl/clair/push.go create mode 100644 cmd/clairctl/clair/push_test.go create mode 100644 cmd/clairctl/clair/report.go create mode 100644 cmd/clairctl/clair/report_test.go create mode 100644 cmd/clairctl/clair/samples/clair_report.json create mode 100644 cmd/clairctl/clair/templates.go create mode 100644 cmd/clairctl/clair/templates/analysis-template.html create mode 100644 cmd/clairctl/clair/versions.go create mode 100644 cmd/clairctl/clairctl.yml.default create mode 100644 cmd/clairctl/cmd/analyze.go create mode 100644 cmd/clairctl/cmd/health.go create mode 100644 cmd/clairctl/cmd/pull.go create mode 100644 cmd/clairctl/cmd/push.go create mode 100644 cmd/clairctl/cmd/report.go create mode 100644 cmd/clairctl/cmd/root.go create mode 100644 cmd/clairctl/cmd/version.go create mode 100644 cmd/clairctl/config/config.go create mode 100644 cmd/clairctl/config/config_test.go create mode 100644 cmd/clairctl/contrib/.hyperclair.yml create mode 100644 cmd/clairctl/contrib/README.md create mode 100644 cmd/clairctl/contrib/auth_server/config/auth_config.yml create mode 100644 cmd/clairctl/contrib/auth_server/ssl/old/server.key create mode 100644 cmd/clairctl/contrib/auth_server/ssl/old/server.pem create mode 100644 cmd/clairctl/contrib/auth_server/ssl/server.key create mode 100644 cmd/clairctl/contrib/auth_server/ssl/server.pem create mode 100644 cmd/clairctl/contrib/config/clair.yml create mode 100644 cmd/clairctl/contrib/docker-compose.yml create mode 100644 cmd/clairctl/contrib/ssl_registry/registry.crt create mode 100644 cmd/clairctl/contrib/ssl_registry/registry.key create mode 100644 cmd/clairctl/docker/docker.go create mode 100644 cmd/clairctl/docker/dockercli/dockercli.go create mode 100644 cmd/clairctl/docker/dockerdist/auth.go create mode 100644 cmd/clairctl/docker/dockerdist/dockerdist.go create mode 100644 cmd/clairctl/main.go create mode 100644 cmd/clairctl/server/server.go create mode 100644 cmd/clairctl/test/test.go create mode 100644 cmd/clairctl/xstrings/xstrings.go create mode 100644 cmd/clairctl/xstrings/xstrings_test.go diff --git a/cmd/clairctl/.gitignore b/cmd/clairctl/.gitignore new file mode 100644 index 00000000..41ef3478 --- /dev/null +++ b/cmd/clairctl/.gitignore @@ -0,0 +1,3 @@ +clairctl +clairctl.yml +reports/ diff --git a/cmd/clairctl/.travis.yml b/cmd/clairctl/.travis.yml new file mode 100644 index 00000000..78602d1e --- /dev/null +++ b/cmd/clairctl/.travis.yml @@ -0,0 +1,25 @@ +language: go +go: + - 1.7 + +install: + - go get -v github.com/Masterminds/glide + - cd $GOPATH/src/github.com/Masterminds/glide && git checkout tags/v0.12.3 && go install && cd - + - glide install -v + +script: +- go build -ldflags "-X github.com/jgsqware/clairctl/cmd.version=$(cat VERSION)" -v + +deploy: + provider: releases + api_key: + secure: SPeZzw212p/0iYPLVWUkLq226j19oWHBogHIlbKzkp8zNk7tdJEHMthAhka1zxS5afr+JWu3BBdduPHbnvvpXtv2axsrCXVsW7jPEcxXgfM3m/YSxQwjsGojMG318nX/E5ApY8eDhiZuHoTAP67DJZEoLteV5GKvNk7np74bMNexBxCPDIbXepBbjrEqxrUG/uVDFrmFf0LlCKVGxALK42uriaDM1tPGDy0+7zX7a3RqG9J4ROmZCQNzd9+rcur4DndrxYPCvJK3awUwXL5XzdRed24m5S8IG6Q/gMMUhUVECMNZ6/Z+KZ4CKqcQ9e9NvOtHYvO0OtPNrd4/HajZpUpfO008imSj6/g9NxdC6hJYvyK/HuRv5DsLgZukyvwroVSM5rC77FJeOmoLXfEBHo7E16I5XKGcp31NoCv5kdQuaHLxkBZk43CcnT/rraQ8cjCQAERQEh6u0fidyOvZ4vkTW30/c6H25zDejCzDcx1oHL4O8QwJwMfRSifSA8vk81saOurmQeviZK1UkpeWLxMYLpFZ8vN9kK/2Nn24ud9DN7N8bqGob13I8lJ69j9xcXuq/86vITziESZCHaYs2Hw1vSHD8vpkQxu8aSYqPXgwAKDwV9JkGV1/kcUdwpOlD/xkKunluu8Mf5K2JSm9G8xWr6foOibKsuHDn0nH+jY= + file: clairctl + skip_cleanup: true + on: + repo: jgsqware/clairctl + tags: true + branch: master +env: + global: + secure: QpHC0r/d3sUNZ4Tisu7IjRnE+exzUUdTRlWUvvepWA2/wdsvsh9IFKmPgHvmt/V46RtByc52HVwE/uE6gF8eVhRs4L4DP/gSi0TxmfNi2m+6EdL1MmFvdIVGfPZDDbFnNNXpWcNJGv5/UDJ8eke003Mtm7/qQfJqH3UzxoWnTrvsA7IHbhxomgsHOFqHU+e6fmaQ3q7neUJEci2HSPLehl11yBwMcsYyFjeW+33GVQeI0m2j1TrcldG3KN63tszIx7tHXTUrIK369nrndLjYWnc7Hi+k6Uc8vPh111/hLBG4UtxffP+/DTkhEtzxDNn3hn4zSKTMIyeKA/UhnySYchzDYOdVZoM0oVxAkbTcdTPWCxS75W1u3AW3QAGxScNEqmy5enrW+kGyRpd1zIRJ1qS2tHAVmos8N6bl5Rp9UJF5UITr9/DyQS93O9DOSY5Z6UVGI5J+MlG89Nr4XJ5NW4ak5yC5fc+9IwM58VMk8ULS3I0g/YBWsWRWHGZrHurnvcGbiXHk5S+anQsiYUV8n3OUa67ai7Duil3qiPklEhHeFZzmXrJMvLdeVxywfvIfKzSEGqDrUYw3hO9pkMzLxDQfvaNzAHWZGJ970N7apX2pnAUgPDsNb8cOnbDSjX36FICAiUZlrkpsz6O6h3Fm/2t/HDCyYH7gf6eJXypUOYc= diff --git a/cmd/clairctl/LICENSE b/cmd/clairctl/LICENSE new file mode 100644 index 00000000..e06d2081 --- /dev/null +++ b/cmd/clairctl/LICENSE @@ -0,0 +1,202 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/cmd/clairctl/README.md b/cmd/clairctl/README.md new file mode 100644 index 00000000..d9b3c95a --- /dev/null +++ b/cmd/clairctl/README.md @@ -0,0 +1,58 @@ +# clairctl +[![Build Status](https://travis-ci.org/jgsqware/clairctl.svg?branch=master)](https://travis-ci.org/jgsqware/clairctl) + +> Tracking container vulnerabilities with Clair Control + +Clairctl is a lightweight command-line tool doing the bridge between Registries as Docker Hub, Docker Registry or Quay.io, and the CoreOS vulnerability tracker, Clair. +Clairctl will play as reverse proxy for authentication. + +# Usage + +[![asciicast](https://asciinema.org/a/41461.png)](https://asciinema.org/a/41461) + +# Reporting + +**clairctl** get vulnerabilities report from Clair and generate HTML report + +clairctl can be used for Docker Hub and self-hosted Registry + +# Command + +``` +Analyze your docker image with Clair, directly from your registry. + +Usage: + clairctl [command] + +Available Commands: + analyze Analyze Docker image + health Get Health of clairctl and underlying services + login Log in to a Docker registry + logout Log out from a Docker registry + pull Pull Docker image information + push Push Docker image to Clair + report Generate Docker Image vulnerabilities report + version Get Versions of clairctl and underlying services + +Flags: + --config string config file (default is ./.clairctl.yml) + --log-level string log level [Panic,Fatal,Error,Warn,Info,Debug] + +Use "clairctl [command] --help" for more information about a command. +``` + +# Optional Configuration + +```yaml +clair: + port: 6060 + healthPort: 6061 + uri: http://clair + report: + path: ./reports + format: html +``` + +# Contribution and Test + +Go to /contrib folder diff --git a/cmd/clairctl/VERSION b/cmd/clairctl/VERSION new file mode 100644 index 00000000..d2d61a7e --- /dev/null +++ b/cmd/clairctl/VERSION @@ -0,0 +1 @@ +1.2.2 \ No newline at end of file diff --git a/cmd/clairctl/clair/analyze.go b/cmd/clairctl/clair/analyze.go new file mode 100644 index 00000000..8e5ca1de --- /dev/null +++ b/cmd/clairctl/clair/analyze.go @@ -0,0 +1,73 @@ +package clair + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/coreos/clair/api/v1" + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/reference" +) + +//Analyze return Clair Image analysis +func Analyze(image reference.NamedTagged, manifest distribution.Manifest) ImageAnalysis { + layers, err := newLayering(image) + if err != nil { + log.Fatalf("cannot parse manifest") + return ImageAnalysis{} + } + + switch manifest.(type) { + case schema1.SignedManifest: + + for _, l := range manifest.(schema1.SignedManifest).FSLayers { + layers.digests = append(layers.digests, l.BlobSum.String()) + } + return layers.analyze() + case *schema1.SignedManifest: + for _, l := range manifest.(*schema1.SignedManifest).FSLayers { + layers.digests = append(layers.digests, l.BlobSum.String()) + } + return layers.analyze() + case schema2.DeserializedManifest: + log.Debugf("json: %v", image) + for _, l := range manifest.(schema2.DeserializedManifest).Layers { + layers.digests = append(layers.digests, l.Digest.String()) + } + return layers.analyze() + case *schema2.DeserializedManifest: + log.Debugf("json: %v", image) + for _, l := range manifest.(*schema2.DeserializedManifest).Layers { + layers.digests = append(layers.digests, l.Digest.String()) + } + return layers.analyze() + default: + log.Fatalf("Unsupported Schema version.") + return ImageAnalysis{} + } +} + +func analyzeLayer(id string) (v1.LayerEnvelope, error) { + + lURI := fmt.Sprintf("%v/layers/%v?vulnerabilities", uri, id) + response, err := http.Get(lURI) + if err != nil { + return v1.LayerEnvelope{}, fmt.Errorf("analysing layer %v: %v", id, err) + } + defer response.Body.Close() + + var analysis v1.LayerEnvelope + err = json.NewDecoder(response.Body).Decode(&analysis) + if err != nil { + return v1.LayerEnvelope{}, fmt.Errorf("reading layer analysis: %v", err) + } + if response.StatusCode != 200 { + //TODO(jgsqware): should I show reponse body in case of error? + return v1.LayerEnvelope{}, fmt.Errorf("receiving http error: %d", response.StatusCode) + } + + return analysis, nil +} diff --git a/cmd/clairctl/clair/clair.go b/cmd/clairctl/clair/clair.go new file mode 100644 index 00000000..0a10785c --- /dev/null +++ b/cmd/clairctl/clair/clair.go @@ -0,0 +1,63 @@ +package clair + +import ( + "strconv" + "strings" + + "github.com/coreos/clair/api/v1" + "github.com/coreos/pkg/capnslog" + "github.com/jgsqware/clairctl/xstrings" + "github.com/spf13/viper" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "clair") + +var uri string +var healthURI string + +//ImageAnalysis Full image analysis +type ImageAnalysis struct { + Registry, ImageName, Tag string + Layers []v1.LayerEnvelope +} + +func (imageAnalysis ImageAnalysis) String() string { + return imageAnalysis.Registry + "/" + imageAnalysis.ImageName + ":" + imageAnalysis.Tag +} + +//LastLayer return the last layer of ImageAnalysis +func (imageAnalysis ImageAnalysis) LastLayer() *v1.Layer { + return imageAnalysis.Layers[len(imageAnalysis.Layers)-1].Layer +} + +func (imageAnalysis ImageAnalysis) CountVulnerabilities(l v1.Layer) int { + count := 0 + for _, feature := range l.Features { + count += len(feature.Vulnerabilities) + } + return count +} + +func fmtURI(u string, port int) string { + + if port != 0 { + u += ":" + strconv.Itoa(port) + } + if !strings.HasPrefix(u, "http://") && !strings.HasPrefix(u, "https://") { + u = "http://" + u + } + + return u +} + +func (imageAnalysis ImageAnalysis) ShortName(l v1.Layer) string { + return xstrings.Substr(l.Name, 0, 12) +} + +//Config configure Clair from configFile +func Config() { + uri = fmtURI(viper.GetString("clair.uri"), viper.GetInt("clair.port")) + "/v1" + healthURI = fmtURI(viper.GetString("clair.uri"), viper.GetInt("clair.healthPort")) + "/health" + Report.Path = viper.GetString("clair.report.path") + Report.Format = viper.GetString("clair.report.format") +} diff --git a/cmd/clairctl/clair/clair_test.go b/cmd/clairctl/clair/clair_test.go new file mode 100644 index 00000000..c3a4e617 --- /dev/null +++ b/cmd/clairctl/clair/clair_test.go @@ -0,0 +1,65 @@ +package clair + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/coreos/pkg/capnslog" +) + +func getSampleAnalysis() []byte { + file, err := ioutil.ReadFile("./samples/clair_report.json") + if err != nil { + log.Errorf("File error: %v\n", err) + } + + return file +} + +func newServer(httpStatus int) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(httpStatus) + })) +} + +func TestIsHealthy(t *testing.T) { + server := newServer(http.StatusOK) + defer server.Close() + healthURI = server.URL + if h := IsHealthy(); !h { + t.Errorf("IsHealthy() => %v, want %v", h, true) + } +} + +func TestIsNotHealthy(t *testing.T) { + server := newServer(http.StatusInternalServerError) + defer server.Close() + uri = server.URL + if h := IsHealthy(); h { + t.Errorf("IsHealthy() => %v, want %v", h, true) + } +} + +func TestRelativeCount(t *testing.T) { + var analysis ImageAnalysis + err := json.Unmarshal([]byte(getSampleAnalysis()), &analysis) + if err != nil { + t.Errorf("Failing with error: %v", err) + } + + vulnerabilitiesCount := allVulnerabilities(analysis) + if vulnerabilitiesCount.RelativeCount("High") != 1.3 { + t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"High\") => %v, want 1.3", vulnerabilitiesCount.RelativeCount("High")) + } + + if vulnerabilitiesCount.RelativeCount("Medium") != 23.38 { + t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Medium\") => %v, want 23.38", vulnerabilitiesCount.RelativeCount("Medium")) + } + + if vulnerabilitiesCount.RelativeCount("Low") != 74.03 { + t.Errorf("analysis.CountAllVulnerabilities().RelativeCount(\"Low\") => %v, want 74.03", vulnerabilitiesCount.RelativeCount("Low")) + } +} diff --git a/cmd/clairctl/clair/health.go b/cmd/clairctl/clair/health.go new file mode 100644 index 00000000..6f39a312 --- /dev/null +++ b/cmd/clairctl/clair/health.go @@ -0,0 +1,20 @@ +package clair + +import "net/http" + +//IsHealthy return Health clair result +func IsHealthy() bool { + log.Debug("requesting health on: " + healthURI) + response, err := http.Get(healthURI) + if err != nil { + log.Errorf("requesting Clair health: %v", err) + return false + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return false + } + + return true +} diff --git a/cmd/clairctl/clair/layering.go b/cmd/clairctl/clair/layering.go new file mode 100644 index 00000000..12bc6a5b --- /dev/null +++ b/cmd/clairctl/clair/layering.go @@ -0,0 +1,102 @@ +package clair + +import ( + "fmt" + "strings" + + "github.com/coreos/clair/api/v1" + "github.com/docker/docker/reference" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/xstrings" +) + +type layering struct { + image reference.NamedTagged + digests []string + parentID, hURL string +} + +func newLayering(image reference.NamedTagged) (*layering, error) { + layer := layering{ + parentID: "", + image: image, + } + + localIP, err := config.LocalServerIP() + if err != nil { + return nil, err + } + layer.hURL = fmt.Sprintf("http://%v/v2", localIP) + if config.IsLocal { + layer.hURL = strings.Replace(layer.hURL, "/v2", "/local", -1) + log.Infof("using %v as local url", layer.hURL) + } + return &layer, nil +} + +func (layer *layering) pushAll() error { + layerCount := len(layer.digests) + + if layerCount == 0 { + log.Warning("there is no layer to push") + } + for index, digest := range layer.digests { + + if config.IsLocal { + digest = strings.TrimPrefix(digest, "sha256:") + } + + lUID := xstrings.Substr(digest, 0, 12) + log.Infof("Pushing Layer %d/%d [%v]", index+1, layerCount, lUID) + + insertRegistryMapping(digest, layer.image.Hostname()) + payload := v1.LayerEnvelope{Layer: &v1.Layer{ + Name: digest, + Path: blobsURI(layer.image.Hostname(), layer.image.RemoteName(), digest), + ParentName: layer.parentID, + Format: "Docker", + }} + + //FIXME Update to TLS + if config.IsLocal { + payload.Layer.Path += "/layer.tar" + } + payload.Layer.Path = strings.Replace(payload.Layer.Path, layer.image.Hostname(), layer.hURL, 1) + if err := pushLayer(payload); err != nil { + log.Infof("adding layer %d/%d [%v]: %v", index+1, layerCount, lUID, err) + if err != ErrUnanalizedLayer { + return err + } + layer.parentID = "" + } else { + layer.parentID = payload.Layer.Name + } + } + return nil +} + +func (layers *layering) analyze() ImageAnalysis { + c := len(layers.digests) + res := []v1.LayerEnvelope{} + + for i := range layers.digests { + digest := layers.digests[c-i-1] + if config.IsLocal { + digest = strings.TrimPrefix(digest, "sha256:") + } + lShort := xstrings.Substr(digest, 0, 12) + + if a, err := analyzeLayer(digest); err != nil { + log.Errorf("analysing layer [%v] %d/%d: %v", lShort, i+1, c, err) + } else { + log.Infof("analysing layer [%v] %d/%d", lShort, i+1, c) + res = append(res, a) + } + } + return ImageAnalysis{ + Registry: xstrings.TrimPrefixSuffix(layers.image.Hostname(), "http://", "/v2"), + ImageName: layers.image.Name(), + Tag: layers.image.Tag(), + Layers: res, + } +} diff --git a/cmd/clairctl/clair/push.go b/cmd/clairctl/clair/push.go new file mode 100644 index 00000000..c1cca5fe --- /dev/null +++ b/cmd/clairctl/clair/push.go @@ -0,0 +1,108 @@ +package clair + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "net/http" + "strings" + + "github.com/coreos/clair/api/v1" + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/reference" + "github.com/jgsqware/clairctl/docker/dockerdist" +) + +// ErrUnanalizedLayer is returned when the layer was not correctly analyzed +var ErrUnanalizedLayer = errors.New("layer cannot be analyzed") + +var registryMapping map[string]string + +func Push(image reference.NamedTagged, manifest distribution.Manifest) error { + layers, err := newLayering(image) + if err != nil { + return err + } + + switch manifest.(type) { + case schema1.SignedManifest: + for _, l := range manifest.(schema1.SignedManifest).FSLayers { + layers.digests = append(layers.digests, l.BlobSum.String()) + } + return layers.pushAll() + case *schema1.SignedManifest: + for _, l := range manifest.(*schema1.SignedManifest).FSLayers { + layers.digests = append(layers.digests, l.BlobSum.String()) + } + return layers.pushAll() + case schema2.DeserializedManifest: + for _, l := range manifest.(schema2.DeserializedManifest).Layers { + layers.digests = append(layers.digests, l.Digest.String()) + } + return layers.pushAll() + case *schema2.DeserializedManifest: + for _, l := range manifest.(*schema2.DeserializedManifest).Layers { + layers.digests = append(layers.digests, l.Digest.String()) + } + return layers.pushAll() + default: + return errors.New("Unsupported Schema version.") + } +} + +func pushLayer(layer v1.LayerEnvelope) error { + lJSON, err := json.Marshal(layer) + if err != nil { + return fmt.Errorf("marshalling layer: %v", err) + } + + lURI := fmt.Sprintf("%v/layers", uri) + request, err := http.NewRequest("POST", lURI, bytes.NewBuffer(lJSON)) + if err != nil { + return fmt.Errorf("creating 'add layer' request: %v", err) + } + request.Header.Set("Content-Type", "application/json") + + response, err := (&http.Client{}).Do(request) + + if err != nil { + return fmt.Errorf("pushing layer to clair: %v", err) + } + defer response.Body.Close() + + if response.StatusCode != 201 { + if response.StatusCode == 422 { + return ErrUnanalizedLayer + } + return fmt.Errorf("receiving http error: %d", response.StatusCode) + } + + return nil +} + +func blobsURI(registry string, name string, digest string) string { + return strings.Join([]string{registry, name, "blobs", digest}, "/") +} + +func insertRegistryMapping(layerDigest string, registryURI string) { + + hostURL, _ := dockerdist.GetPushURL(registryURI) + log.Debugf("Saving %s[%s]", layerDigest, hostURL.String()) + registryMapping[layerDigest] = hostURL.String() +} + +//GetRegistryMapping return the registryURI corresponding to the layerID passed as parameter +func GetRegistryMapping(layerDigest string) (string, error) { + registryURI, present := registryMapping[layerDigest] + if !present { + return "", fmt.Errorf("%v mapping not found", layerDigest) + } + return registryURI, nil +} + +func init() { + registryMapping = map[string]string{} +} diff --git a/cmd/clairctl/clair/push_test.go b/cmd/clairctl/clair/push_test.go new file mode 100644 index 00000000..ab2bf914 --- /dev/null +++ b/cmd/clairctl/clair/push_test.go @@ -0,0 +1,28 @@ +package clair + +import "testing" + +func TestInsertRegistryMapping(t *testing.T) { + layerID := "sha256:13be4a52fdee2f6c44948b99b5b65ec703b1ca76c1ab5d2d90ae9bf18347082e" + registryURI := "registry:5000" + insertRegistryMapping(layerID, registryURI) + + if r := registryMapping[layerID]; r != "http://registry:5000/v2" { + t.Errorf("insertRegistryMapping(%q,%q) => %q, want %q", layerID, registryURI, r, "http://registry:5000/v2") + } +} + +func TestGetRegistryMapping(t *testing.T) { + layerID := "sha256:13be4a52fdee2f6c44948b99b5b65ec703b1ca76c1ab5d2d90ae9bf18347082e" + registryURI := "registry:5000" + insertRegistryMapping(layerID, registryURI) + + if r, err := GetRegistryMapping(layerID); r != "http://registry:5000/v2" { + + if err != nil { + t.Errorf("GetRegistryMapping(%q) failed => %v", layerID, err) + } else { + t.Errorf("GetRegistryMapping(%q) => %q, want %q", layerID, registryURI, r) + } + } +} diff --git a/cmd/clairctl/clair/report.go b/cmd/clairctl/clair/report.go new file mode 100644 index 00000000..1e9efce1 --- /dev/null +++ b/cmd/clairctl/clair/report.go @@ -0,0 +1,141 @@ +package clair + +import ( + "bytes" + "fmt" + "math" + "text/template" + + "github.com/coreos/clair/api/v1" + "github.com/coreos/clair/utils/types" +) + +//execute go generate ./clair +//go:generate go-bindata -pkg clair -o templates.go templates/... + +//Report Reporting Config value +var Report ReportConfig + +//ReportConfig Reporting configuration +type ReportConfig struct { + Path string + Format string +} + +//ReportAsHTML report analysis as HTML +func ReportAsHTML(analyzes ImageAnalysis) (string, error) { + asset, err := Asset("templates/analysis-template.html") + if err != nil { + return "", fmt.Errorf("accessing template: %v", err) + } + + funcs := template.FuncMap{ + "vulnerabilities": vulnerabilities, + "allVulnerabilities": allVulnerabilities, + "sortedVulnerabilities": sortedVulnerabilities, + } + + templte := template.Must(template.New("analysis-template").Funcs(funcs).Parse(string(asset))) + + var doc bytes.Buffer + err = templte.Execute(&doc, analyzes) + if err != nil { + return "", fmt.Errorf("rendering HTML report: %v", err) + } + return doc.String(), nil +} + +func invertedPriorities() []types.Priority { + ip := make([]types.Priority, len(types.Priorities)) + for i, j := 0, len(types.Priorities)-1; i <= j; i, j = i+1, j-1 { + ip[i], ip[j] = types.Priorities[j], types.Priorities[i] + } + return ip + +} + +type vulnerabilityWithFeature struct { + v1.Vulnerability + Feature string +} + +//VulnerabiliesCounts Total count of vulnerabilities by type +type vulnerabiliesCounts map[types.Priority]int + +//Total return to total of Vulnerabilities +func (v vulnerabiliesCounts) Total() int { + var c int + for _, count := range v { + c += count + } + return c +} + +//Count return count of severities in Vulnerabilities +func (v vulnerabiliesCounts) Count(severity string) int { + return v[types.Priority(severity)] +} + +//RelativeCount get the percentage of vulnerabilities of a severity +func (v vulnerabiliesCounts) RelativeCount(severity string) float64 { + count := v[types.Priority(severity)] + result := float64(count) / float64(v.Total()) * 100 + return math.Ceil(result*100) / 100 +} + +// allVulnerabilities Total count of vulnerabilities +func allVulnerabilities(imageAnalysis ImageAnalysis) vulnerabiliesCounts { + result := make(vulnerabiliesCounts) + + l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1] + + for _, f := range l.Layer.Features { + + for _, v := range f.Vulnerabilities { + result[types.Priority(v.Severity)]++ + } + } + + return result +} + +//Vulnerabilities return a list a vulnerabilities +func vulnerabilities(imageAnalysis ImageAnalysis) map[types.Priority][]vulnerabilityWithFeature { + + result := make(map[types.Priority][]vulnerabilityWithFeature) + + l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1] + for _, f := range l.Layer.Features { + for _, v := range f.Vulnerabilities { + + result[types.Priority(v.Severity)] = append(result[types.Priority(v.Severity)], vulnerabilityWithFeature{Vulnerability: v, Feature: f.Name + ":" + f.Version}) + } + } + + return result +} + +// SortedVulnerabilities get all vulnerabilities sorted by Severity +func sortedVulnerabilities(imageAnalysis ImageAnalysis) []v1.Feature { + features := []v1.Feature{} + + l := imageAnalysis.Layers[len(imageAnalysis.Layers)-1] + + for _, f := range l.Layer.Features { + if len(f.Vulnerabilities) > 0 { + vulnerabilities := []v1.Vulnerability{} + for _, p := range invertedPriorities() { + for _, v := range f.Vulnerabilities { + if types.Priority(v.Severity) == p { + vulnerabilities = append(vulnerabilities, v) + } + } + } + nf := f + nf.Vulnerabilities = vulnerabilities + features = append(features, nf) + } + } + + return features +} diff --git a/cmd/clairctl/clair/report_test.go b/cmd/clairctl/clair/report_test.go new file mode 100644 index 00000000..b8e0134e --- /dev/null +++ b/cmd/clairctl/clair/report_test.go @@ -0,0 +1,39 @@ +package clair + +import ( + "encoding/json" + "io/ioutil" + "os" + "testing" + + "github.com/coreos/clair/utils/types" +) + +func TestReportAsHtml(t *testing.T) { + var analysis ImageAnalysis + err := json.Unmarshal([]byte(getSampleAnalysis()), &analysis) + + if err != nil { + t.Errorf("Failing with error: %v", err) + } + + html, err := ReportAsHTML(analysis) + if err != nil { + log.Fatal(err) + } + + err = ioutil.WriteFile(os.TempDir()+"/clairctl-html-report.html", []byte(html), 0700) + if err != nil { + log.Fatal(err) + } +} + +func TestInvertedPriorities(t *testing.T) { + expected := []types.Priority{types.Defcon1, types.Critical, types.High, types.Medium, types.Low, types.Negligible, types.Unknown} + ip := invertedPriorities() + for i, v := range ip { + if v != expected[i] { + t.Errorf("Expecting %v, got %v", expected, ip) + } + } +} diff --git a/cmd/clairctl/clair/samples/clair_report.json b/cmd/clairctl/clair/samples/clair_report.json new file mode 100644 index 00000000..430e491f --- /dev/null +++ b/cmd/clairctl/clair/samples/clair_report.json @@ -0,0 +1,1558 @@ +{ + "Registry": "registry:5000", + "ImageName": "jgsqware/ubuntu-git", + "Tag": "latest", + "Layers": [ + { + "Layer": { + "Name": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845", + "Namespace": "ubuntu:14.04", + "ParentName": "sha256:9e0bc8a71bde464f710bc2b593a1fc21521517671e918687892303151331fa56", + "IndexedByVersion": 2, + "Features": [ + { + "Name": "sudo", + "Namespace": "ubuntu:14.04", + "Version": "1.8.9p5-1ubuntu1.2", + "Vulnerabilities": [ + { + "Name": "CVE-2015-8239", + "Namespace": "ubuntu:14.04", + "Description": "race condition checking digests/checksums in sudoers", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8239", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-5602", + "Namespace": "ubuntu:14.04", + "Description": "sudoedit in Sudo before 1.8.15 allows local users to gain privileges via a symlink attack on a file whose full path is defined using multiple wildcards in /etc/sudoers, as demonstrated by \"/home/*/*/file.txt.\"", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-5602", + "Severity": "Medium" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libtext-soundex-perl", + "Namespace": "ubuntu:14.04", + "Version": "3.4-1build1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "dash", + "Namespace": "ubuntu:14.04", + "Version": "0.5.7-4ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "eject", + "Namespace": "ubuntu:14.04", + "Version": "2.1.5+deb1+cvs20081104-13.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gcc-4.8", + "Namespace": "ubuntu:14.04", + "Version": "4.8.4-2ubuntu1~14.04", + "Vulnerabilities": [ + { + "Name": "CVE-2014-5044", + "Namespace": "ubuntu:14.04", + "Description": "Array memory allocations could cause an integer overflow and thus memory overflow issues at runtime.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-5044", + "Severity": "Low" + }, + { + "Name": "CVE-2015-5276", + "Namespace": "ubuntu:14.04", + "Description": "The std::random_device class in libstdc++ in the GNU Compiler Collection (aka GCC) before 4.9.4 does not properly handle short reads from blocking sources, which makes it easier for context-dependent attackers to predict the random values via unspecified vectors.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-5276", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "lvm2", + "Namespace": "ubuntu:14.04", + "Version": "2.02.98-6ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "net-tools", + "Namespace": "ubuntu:14.04", + "Version": "1.60-25ubuntu2.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libsepol", + "Namespace": "ubuntu:14.04", + "Version": "2.2-1ubuntu0.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libtext-charwidth-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.04-7build3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "cdebconf", + "Namespace": "ubuntu:14.04", + "Version": "0.187ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "vim", + "Namespace": "ubuntu:14.04", + "Version": "2:7.4.052-1ubuntu3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "attr", + "Namespace": "ubuntu:14.04", + "Version": "1:2.4.47-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gnutls26", + "Namespace": "ubuntu:14.04", + "Version": "2.12.23-12ubuntu2.3", + "Vulnerabilities": [ + { + "Name": "CVE-2015-7575", + "Namespace": "ubuntu:14.04", + "Description": "Mozilla Network Security Services (NSS) before 3.20.2, as used in Mozilla Firefox before 43.0.2 and Firefox ESR 38.x before 38.5.2, does not reject MD5 signatures in Server Key Exchange messages in TLS 1.2 Handshake Protocol traffic, which makes it easier for man-in-the-middle attackers to spoof servers by triggering a collision.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7575", + "Severity": "Medium", + "FixedBy": "2.12.23-12ubuntu2.4" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "python3.4", + "Namespace": "ubuntu:14.04", + "Version": "3.4.3-1ubuntu1~14.04.3", + "Vulnerabilities": [ + { + "Name": "CVE-2014-2667", + "Namespace": "ubuntu:14.04", + "Description": "Race condition in the _get_masked_mode function in Lib/os.py in Python 3.2 through 3.5, when exist_ok is set to true and multiple threads are used, might allow local users to bypass intended file permissions by leveraging a separate application vulnerability before the umask has been set to the expected value.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-2667", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "logrotate", + "Namespace": "ubuntu:14.04", + "Version": "3.8.7-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "adduser", + "Namespace": "ubuntu:14.04", + "Version": "3.113+nmu3ubuntu3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "base-passwd", + "Namespace": "ubuntu:14.04", + "Version": "3.5.33", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "grep", + "Namespace": "ubuntu:14.04", + "Version": "2.16-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "dpkg", + "Namespace": "ubuntu:14.04", + "Version": "1.17.5ubuntu5.5", + "Vulnerabilities": [ + { + "Name": "CVE-2014-8625", + "Namespace": "ubuntu:14.04", + "Description": "Multiple format string vulnerabilities in the parse_error_msg function in parsehelp.c in dpkg before 1.17.22 allow remote attackers to cause a denial of service (crash) and possibly execute arbitrary code via format string specifiers in the (1) package or (2) architecture name.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-8625", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ustr", + "Namespace": "ubuntu:14.04", + "Version": "1.0.4-3ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "db5.3", + "Namespace": "ubuntu:14.04", + "Version": "5.3.28-3ubuntu3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libtext-iconv-perl", + "Namespace": "ubuntu:14.04", + "Version": "1.7-5build2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gccgo-4.9", + "Namespace": "ubuntu:14.04", + "Version": "4.9.1-0ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "xz-utils", + "Namespace": "ubuntu:14.04", + "Version": "5.1.1alpha+20120614-2ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "netcat-openbsd", + "Namespace": "ubuntu:14.04", + "Version": "1.105-7ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "coreutils", + "Namespace": "ubuntu:14.04", + "Version": "8.21-1ubuntu5.3", + "Vulnerabilities": [ + { + "Name": "CVE-2015-1865", + "Namespace": "ubuntu:14.04", + "Description": "\"time of check to time of use\" race condition fts.c", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-1865", + "Severity": "Low" + }, + { + "Name": "CVE-2016-2781", + "Namespace": "ubuntu:14.04", + "Description": "nonpriv session can escape to the parent session by using the TIOCSTI ioctl", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2781", + "Severity": "Medium" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "cpio", + "Namespace": "ubuntu:14.04", + "Version": "2.11+dfsg-1ubuntu1.1", + "Vulnerabilities": [ + { + "Name": "CVE-2016-2037", + "Namespace": "ubuntu:14.04", + "Description": "The cpio_safer_name_suffix function in util.c in cpio 2.11 allows remote attackers to cause a denial of service (out-of-bounds write) via a crafted cpio file.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2037", + "Severity": "Medium", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 4.3, + "Vectors": "AV:N/AC:M/Au:N/C:N/I:N" + } + } + }, + "FixedBy": "2.11+dfsg-1ubuntu1.2" + }, + { + "Name": "CVE-2015-1197", + "Namespace": "ubuntu:14.04", + "Description": "cpio 2.11, when using the --no-absolute-filenames option, allows local users to write to arbitrary files via a symlink attack on a file in an archive.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-1197", + "Severity": "Low", + "FixedBy": "2.11+dfsg-1ubuntu1.2" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "tar", + "Namespace": "ubuntu:14.04", + "Version": "1.27.1-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libffi", + "Namespace": "ubuntu:14.04", + "Version": "3.1~rc1+r3.0.13-12ubuntu0.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libgcrypt11", + "Namespace": "ubuntu:14.04", + "Version": "1.5.3-2ubuntu4.2", + "Vulnerabilities": [ + { + "Name": "CVE-2015-7511", + "Namespace": "ubuntu:14.04", + "Description": "ECDH Key-Extraction via Low-Bandwidth Electromagnetic Attacks on PCs", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7511", + "Severity": "Medium", + "FixedBy": "1.5.3-2ubuntu4.3" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "findutils", + "Namespace": "ubuntu:14.04", + "Version": "4.4.2-7", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gzip", + "Namespace": "ubuntu:14.04", + "Version": "1.6-3ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "klibc", + "Namespace": "ubuntu:14.04", + "Version": "2.0.3-0ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gnupg", + "Namespace": "ubuntu:14.04", + "Version": "1.4.16-1ubuntu2.3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "readline6", + "Namespace": "ubuntu:14.04", + "Version": "6.3-4ubuntu2", + "Vulnerabilities": [ + { + "Name": "CVE-2014-2524", + "Namespace": "ubuntu:14.04", + "Description": "The _rl_tropen function in util.c in GNU readline before 6.3 patch 3 allows local users to create or overwrite arbitrary files via a symlink attack on a /var/tmp/rltrace.[PID] file.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-2524", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "lockfile-progs", + "Namespace": "ubuntu:14.04", + "Version": "0.1.17", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "fribidi", + "Namespace": "ubuntu:14.04", + "Version": "0.19.6-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "json-c", + "Namespace": "ubuntu:14.04", + "Version": "0.11-3ubuntu1.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "sysvinit", + "Namespace": "ubuntu:14.04", + "Version": "2.88dsf-41ubuntu6.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "base-files", + "Namespace": "ubuntu:14.04", + "Version": "7.2ubuntu5.3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ifupdown", + "Namespace": "ubuntu:14.04", + "Version": "0.7.47.2ubuntu4.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "resolvconf", + "Namespace": "ubuntu:14.04", + "Version": "1.69ubuntu1.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "bash", + "Namespace": "ubuntu:14.04", + "Version": "4.3-7ubuntu1.5", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "console-setup", + "Namespace": "ubuntu:14.04", + "Version": "1.70ubuntu8", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "pcre3", + "Namespace": "ubuntu:14.04", + "Version": "1:8.31-2ubuntu2.1", + "Vulnerabilities": [ + { + "Name": "CVE-2015-2328", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.36 mishandles the /((?(R)a|(?1)))+/ pattern and related patterns with certain recursion, which allows remote attackers to cause a denial of service (segmentation fault) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-2328", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2016-3191", + "Namespace": "ubuntu:14.04", + "Description": "The compile_branch function in pcre_compile.c in PCRE 8.x before 8.39 and pcre2_compile.c in PCRE2 before 10.22 mishandles patterns containing an (*ACCEPT) substring in conjunction with nested parentheses, which allows remote attackers to execute arbitrary code or cause a denial of service (stack-based buffer overflow) via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror, aka ZDI-CAN-3542.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-3191", + "Severity": "Medium", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8394", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.38 mishandles the (?(\u003cdigits\u003e) and (?(R\u003cdigits\u003e) conditions, which allows remote attackers to cause a denial of service (integer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8394", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8391", + "Namespace": "ubuntu:14.04", + "Description": "The pcre_compile function in pcre_compile.c in PCRE before 8.38 mishandles certain [: nesting, which allows remote attackers to cause a denial of service (CPU consumption) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8391", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 9, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8390", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.38 mishandles the [: and \\\\ substrings in character classes, which allows remote attackers to cause a denial of service (uninitialized memory read) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8390", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8382", + "Namespace": "ubuntu:14.04", + "Description": "The match function in pcre_exec.c in PCRE before 8.37 mishandles the /(?:((abcd))|(((?:(?:(?:(?:abc|(?:abcdef))))b)abcdefghi)abc)|((*ACCEPT)))/ pattern and related patterns involving (*ACCEPT), which allows remote attackers to obtain sensitive information from process memory or cause a denial of service (partially initialized memory and application crash) via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror, aka ZDI-CAN-2547.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8382", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 6.4, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:N" + } + } + } + }, + { + "Name": "CVE-2015-8387", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.38 mishandles (?123) subroutine calls and related subroutine calls, which allows remote attackers to cause a denial of service (integer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8387", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8393", + "Namespace": "ubuntu:14.04", + "Description": "pcregrep in PCRE before 8.38 mishandles the -q option for binary files, which might allow remote attackers to obtain sensitive information via a crafted file, as demonstrated by a CGI script that sends stdout data to a client.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8393", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:N" + } + } + } + }, + { + "Name": "CVE-2015-8386", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.38 mishandles the interaction of lookbehind assertions and mutually recursive subpatterns, which allows remote attackers to cause a denial of service (buffer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8386", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8380", + "Namespace": "ubuntu:14.04", + "Description": "The pcre_exec function in pcre_exec.c in PCRE before 8.38 mishandles a // pattern with a \\01 string, which allows remote attackers to cause a denial of service (heap-based buffer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8380", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + }, + { + "Name": "CVE-2015-8385", + "Namespace": "ubuntu:14.04", + "Description": "PCRE before 8.38 mishandles the /(?|(\\k'Pm')|(?'Pm'))/ pattern and related patterns with certain forward references, which allows remote attackers to cause a denial of service (buffer overflow) or possibly have unspecified other impact via a crafted regular expression, as demonstrated by a JavaScript RegExp object encountered by Konqueror.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8385", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.5, + "Vectors": "AV:N/AC:L/Au:N/C:P/I:P" + } + } + } + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libsemanage", + "Namespace": "ubuntu:14.04", + "Version": "2.2-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "gdbm", + "Namespace": "ubuntu:14.04", + "Version": "1.8.3-12build1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "xkeyboard-config", + "Namespace": "ubuntu:14.04", + "Version": "2.10.1-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ncurses", + "Namespace": "ubuntu:14.04", + "Version": "5.9+20140118-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "python3-defaults", + "Namespace": "ubuntu:14.04", + "Version": "3.4.0-0ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libarchive-extract-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.70-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "iproute2", + "Namespace": "ubuntu:14.04", + "Version": "3.12.0-2ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "liblocale-gettext-perl", + "Namespace": "ubuntu:14.04", + "Version": "1.05-7build3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "mawk", + "Namespace": "ubuntu:14.04", + "Version": "1.3.3-17ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "expat", + "Namespace": "ubuntu:14.04", + "Version": "2.1.0-4ubuntu1.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libcap2", + "Namespace": "ubuntu:14.04", + "Version": "1:2.24-0ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "initramfs-tools", + "Namespace": "ubuntu:14.04", + "Version": "0.103ubuntu4.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libestr", + "Namespace": "ubuntu:14.04", + "Version": "0.1.9-0ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libtext-wrapi18n-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.06-7", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "debianutils", + "Namespace": "ubuntu:14.04", + "Version": "4.4", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "insserv", + "Namespace": "ubuntu:14.04", + "Version": "1.14.0-5ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "slang2", + "Namespace": "ubuntu:14.04", + "Version": "2.2.4-15ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "systemd", + "Namespace": "ubuntu:14.04", + "Version": "204-5ubuntu20.15", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "upstart", + "Namespace": "ubuntu:14.04", + "Version": "1.12.1-0ubuntu4.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "kbd", + "Namespace": "ubuntu:14.04", + "Version": "1.15.5-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "busybox", + "Namespace": "ubuntu:14.04", + "Version": "1:1.21.0-1ubuntu1", + "Vulnerabilities": [ + { + "Name": "CVE-2014-9645", + "Namespace": "ubuntu:14.04", + "Description": "modprobe wrongly accepts paths as module names", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9645", + "Severity": "Low" + }, + { + "Name": "CVE-2011-5325", + "Namespace": "ubuntu:14.04", + "Description": "path traversal vulnerability in busybox tar", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2011-5325", + "Severity": "Medium" + }, + { + "Name": "CVE-2016-2147", + "Namespace": "ubuntu:14.04", + "Description": "OOB heap write due to integer underflow", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2147", + "Severity": "Low" + }, + { + "Name": "CVE-2016-2148", + "Namespace": "ubuntu:14.04", + "Description": "heap overflow in OPTION_6RD parsing", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2148", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "eglibc", + "Namespace": "ubuntu:14.04", + "Version": "2.19-0ubuntu6.6", + "Vulnerabilities": [ + { + "Name": "CVE-2015-8778", + "Namespace": "ubuntu:14.04", + "Description": "hcreate((size_t)-1) should fail with ENOMEM", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8778", + "Severity": "Low" + }, + { + "Name": "CVE-2014-9761", + "Namespace": "ubuntu:14.04", + "Description": "nan function unbounded stack allocation", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9761", + "Severity": "Low" + }, + { + "Name": "CVE-2015-5180", + "Namespace": "ubuntu:14.04", + "Description": "DNS resolver NULL pointer dereference with crafted record type", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-5180", + "Severity": "Low" + }, + { + "Name": "CVE-2013-2207", + "Namespace": "ubuntu:14.04", + "Description": "pt_chown in GNU C Library (aka glibc or libc6) before 2.18 does not properly check permissions for tty files, which allows local users to change the permission on the files and obtain access to arbitrary pseudo-terminals by leveraging a FUSE file system.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2013-2207", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8776", + "Namespace": "ubuntu:14.04", + "Description": "Passing out of range data to strftime() causes a segfault", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8776", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8779", + "Namespace": "ubuntu:14.04", + "Description": "catopen() Multiple unbounded stack allocations", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8779", + "Severity": "Low" + }, + { + "Name": "CVE-2015-5277", + "Namespace": "ubuntu:14.04", + "Description": "The get_contents function in nss_files/files-XXX.c in the Name Service Switch (NSS) in GNU C Library (aka glibc or libc6) before 2.20 might allow local users to cause a denial of service (heap corruption) or gain privileges via a long line in the NSS files database.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-5277", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-7547", + "Namespace": "ubuntu:14.04", + "Description": "Multiple stack-based buffer overflows in the (1) send_dg and (2) send_vc functions in the libresolv library in the GNU C Library (aka glibc or libc6) before 2.23 allow remote attackers to cause a denial of service (crash) or possibly execute arbitrary code via a crafted DNS response that triggers a call to the getaddrinfo function with the AF_UNSPEC or AF_INET6 address family, related to performing \"dual A/AAAA DNS queries\" and the libnss_dns.so.2 NSS module.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7547", + "Severity": "High", + "FixedBy": "2.19-0ubuntu6.7" + }, + { + "Name": "CVE-2014-8121", + "Namespace": "ubuntu:14.04", + "Description": "DB_LOOKUP in nss_files/files-XXX.c in the Name Service Switch (NSS) in GNU C Library (aka glibc or libc6) 2.21 and earlier does not properly check if a file is open, which allows remote attackers to cause a denial of service (infinite loop) by performing a look-up while the database is iterated over the database, which triggers the file pointer to be reset.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-8121", + "Severity": "Low" + }, + { + "Name": "CVE-2015-1781", + "Namespace": "ubuntu:14.04", + "Description": "Buffer overflow in the gethostbyname_r and other unspecified NSS functions in the GNU C Library (aka glibc or libc6) before 2.22 allows context-dependent attackers to cause a denial of service (crash) or execute arbitrary code via a crafted DNS response, which triggers a call with a misaligned buffer.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-1781", + "Severity": "Low" + }, + { + "Name": "CVE-2016-1234", + "Namespace": "ubuntu:14.04", + "Description": "glob: buffer overflow with GLOB_ALTDIRFUNC due to incorrect NAME_MAX limit assumption", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-1234", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-8777", + "Namespace": "ubuntu:14.04", + "Description": "The process_envvars function in elf/rtld.c in the GNU C Library (aka glibc or libc6) before 2.23 allows local users to bypass a pointer-guarding protection mechanism via a zero value of the LD_POINTER_GUARD environment variable.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8777", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "hostname", + "Namespace": "ubuntu:14.04", + "Version": "3.15ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "bzip2", + "Namespace": "ubuntu:14.04", + "Version": "1.0.6-5", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libnih", + "Namespace": "ubuntu:14.04", + "Version": "1.0.3-4ubuntu25", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "mpdecimal", + "Namespace": "ubuntu:14.04", + "Version": "2.4.0-6", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "openssl", + "Namespace": "ubuntu:14.04", + "Version": "1.0.1f-1ubuntu2.16", + "Vulnerabilities": [ + { + "Name": "CVE-2016-0797", + "Namespace": "ubuntu:14.04", + "Description": "Multiple integer overflows in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g allow remote attackers to cause a denial of service (heap memory corruption or NULL pointer dereference) or possibly have unspecified other impact via a long digit string that is mishandled by the (1) BN_dec2bn or (2) BN_hex2bn function, related to crypto/bn/bn.h and crypto/bn/bn_print.c.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0797", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 5, + "Vectors": "AV:N/AC:L/Au:N/C:N/I:N" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + }, + { + "Name": "CVE-2016-2842", + "Namespace": "ubuntu:14.04", + "Description": "The doapr_outch function in crypto/bio/b_print.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g does not verify that a certain memory allocation succeeds, which allows remote attackers to cause a denial of service (out-of-bounds write or memory consumption) or possibly have unspecified other impact via a long string, as demonstrated by a large amount of ASN.1 data, a different vulnerability than CVE-2016-0799.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2842", + "Severity": "Medium", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 10, + "Vectors": "AV:N/AC:L/Au:N/C:C/I:C" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + }, + { + "Name": "CVE-2016-0702", + "Namespace": "ubuntu:14.04", + "Description": "The MOD_EXP_CTIME_COPY_FROM_PREBUF function in crypto/bn/bn_exp.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g does not properly consider cache-bank access times during modular exponentiation, which makes it easier for local users to discover RSA keys by running a crafted application on the same Intel Sandy Bridge CPU core as a victim and leveraging cache-bank conflicts, aka a \"CacheBleed\" attack.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0702", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 1.9, + "Vectors": "AV:L/AC:M/Au:N/C:P/I:N" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + }, + { + "Name": "CVE-2016-0705", + "Namespace": "ubuntu:14.04", + "Description": "Double free vulnerability in the dsa_priv_decode function in crypto/dsa/dsa_ameth.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g allows remote attackers to cause a denial of service (memory corruption) or possibly have unspecified other impact via a malformed DSA private key.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0705", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 10, + "Vectors": "AV:N/AC:L/Au:N/C:C/I:C" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + }, + { + "Name": "CVE-2016-0798", + "Namespace": "ubuntu:14.04", + "Description": "Memory leak in the SRP_VBASE_get_by_user implementation in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g allows remote attackers to cause a denial of service (memory consumption) by providing an invalid username in a connection attempt, related to apps/s_server.c and crypto/srp/srp_vfy.c.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0798", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.8, + "Vectors": "AV:N/AC:L/Au:N/C:N/I:N" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + }, + { + "Name": "CVE-2016-0799", + "Namespace": "ubuntu:14.04", + "Description": "The fmtstr function in crypto/bio/b_print.c in OpenSSL 1.0.1 before 1.0.1s and 1.0.2 before 1.0.2g improperly calculates string lengths, which allows remote attackers to cause a denial of service (overflow and out-of-bounds read) or possibly have unspecified other impact via a long string, as demonstrated by a large amount of ASN.1 data, a different vulnerability than CVE-2016-2842.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0799", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 10, + "Vectors": "AV:N/AC:L/Au:N/C:C/I:C" + } + } + }, + "FixedBy": "1.0.1f-1ubuntu2.18" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "diffutils", + "Namespace": "ubuntu:14.04", + "Version": "1:3.3-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "apt", + "Namespace": "ubuntu:14.04", + "Version": "1.0.1ubuntu2.10", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "debconf", + "Namespace": "ubuntu:14.04", + "Version": "1.5.51ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ubuntu-meta", + "Namespace": "ubuntu:14.04", + "Version": "1.325", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "rsyslog", + "Namespace": "ubuntu:14.04", + "Version": "7.4.4-1ubuntu2.6", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "dh-python", + "Namespace": "ubuntu:14.04", + "Version": "1.20140128-1ubuntu8.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libpng", + "Namespace": "ubuntu:14.04", + "Version": "1.2.50-1ubuntu2.14.04.1", + "Vulnerabilities": [ + { + "Name": "CVE-2015-8540", + "Namespace": "ubuntu:14.04", + "Description": "underflow read in png_check_keyword in pngwutil.c", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8540", + "Severity": "Medium", + "FixedBy": "1.2.50-1ubuntu2.14.04.2" + }, + { + "Name": "CVE-2015-8472", + "Namespace": "ubuntu:14.04", + "Description": "Buffer overflow in the png_set_PLTE function in libpng before 1.0.65, 1.1.x and 1.2.x before 1.2.55, 1.3.x, 1.4.x before 1.4.18, 1.5.x before 1.5.25, and 1.6.x before 1.6.20 allows remote attackers to cause a denial of service (application crash) or possibly have unspecified other impact via a small bit-depth value in an IHDR (aka image header) chunk in a PNG image. NOTE: this vulnerability exists because of an incomplete fix for CVE-2015-8126.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8472", + "Severity": "Medium", + "FixedBy": "1.2.50-1ubuntu2.14.04.2" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libmodule-pluggable-perl", + "Namespace": "ubuntu:14.04", + "Version": "5.1-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "mountall", + "Namespace": "ubuntu:14.04", + "Version": "2.53", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "cron", + "Namespace": "ubuntu:14.04", + "Version": "3.0pl1-124ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "acl", + "Namespace": "ubuntu:14.04", + "Version": "2.2.52-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libdrm", + "Namespace": "ubuntu:14.04", + "Version": "2.4.60-2~ubuntu14.04.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "util-linux", + "Namespace": "ubuntu:14.04", + "Version": "2.20.1-5.1ubuntu20.7", + "Vulnerabilities": [ + { + "Name": "CVE-2014-9114", + "Namespace": "ubuntu:14.04", + "Description": "blkid command injection", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9114", + "Severity": "Low" + }, + { + "Name": "CVE-2013-0157", + "Namespace": "ubuntu:14.04", + "Description": "(a) mount and (b) umount in util-linux 2.14.1, 2.17.2, and probably other versions allow local users to determine the existence of restricted directories by (1) using the --guess-fstype command-line option or (2) attempting to mount a non-existent device, which generates different error messages depending on whether the directory exists.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2013-0157", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "tzdata", + "Namespace": "ubuntu:14.04", + "Version": "2015g-0ubuntu0.14.04", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "less", + "Namespace": "ubuntu:14.04", + "Version": "458-2", + "Vulnerabilities": [ + { + "Name": "CVE-2014-9488", + "Namespace": "ubuntu:14.04", + "Description": "The is_utf8_well_formed function in GNU less before 475 allows remote attackers to have unspecified impact via malformed UTF-8 characters, which triggers an out-of-bounds read.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9488", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "popt", + "Namespace": "ubuntu:14.04", + "Version": "1.16-8ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "init-system-helpers", + "Namespace": "ubuntu:14.04", + "Version": "1.14", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "mime-support", + "Namespace": "ubuntu:14.04", + "Version": "3.54ubuntu1.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "p11-kit", + "Namespace": "ubuntu:14.04", + "Version": "0.20.2-2ubuntu2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "cgmanager", + "Namespace": "ubuntu:14.04", + "Version": "0.24-0ubuntu7.5", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libselinux", + "Namespace": "ubuntu:14.04", + "Version": "2.2.2-1ubuntu0.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "langpack-locales", + "Namespace": "ubuntu:14.04", + "Version": "2.13+git20120306-12.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "procps", + "Namespace": "ubuntu:14.04", + "Version": "1:3.3.9-1ubuntu2.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ureadahead", + "Namespace": "ubuntu:14.04", + "Version": "0.100.0-16", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "file", + "Namespace": "ubuntu:14.04", + "Version": "1:5.14-2ubuntu3.3", + "Vulnerabilities": [ + { + "Name": "CVE-2014-9621", + "Namespace": "ubuntu:14.04", + "Description": "The ELF parser in file 5.16 through 5.21 allows remote attackers to cause a denial of service via a long string.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9621", + "Severity": "Low" + }, + { + "Name": "CVE-2014-9620", + "Namespace": "ubuntu:14.04", + "Description": "The ELF parser in file 5.08 through 5.21 allows remote attackers to cause a denial of service via a large number of notes.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9620", + "Severity": "Low" + }, + { + "Name": "CVE-2014-9653", + "Namespace": "ubuntu:14.04", + "Description": "readelf.c in file before 5.22, as used in the Fileinfo component in PHP before 5.4.37, 5.5.x before 5.5.21, and 5.6.x before 5.6.5, does not consider that pread calls sometimes read only a subset of the available data, which allows remote attackers to cause a denial of service (uninitialized memory access) or possibly have unspecified other impact via a crafted ELF file.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-9653", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "dbus", + "Namespace": "ubuntu:14.04", + "Version": "1.6.18-0ubuntu4.3", + "Vulnerabilities": [ + { + "Name": "CVE-2015-0245", + "Namespace": "ubuntu:14.04", + "Description": "D-Bus 1.4.x through 1.6.x before 1.6.30, 1.8.x before 1.8.16, and 1.9.x before 1.9.10 does not validate the source of ActivationFailure signals, which allows local users to cause a denial of service (activation failure error returned) by leveraging a race condition involving sending an ActivationFailure signal before systemd responds.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-0245", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "zlib", + "Namespace": "ubuntu:14.04", + "Version": "1:1.2.8.dfsg-1ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "netbase", + "Namespace": "ubuntu:14.04", + "Version": "5.2", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libtasn1-6", + "Namespace": "ubuntu:14.04", + "Version": "3.4-3ubuntu0.3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "sed", + "Namespace": "ubuntu:14.04", + "Version": "4.2.2-4ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "e2fsprogs", + "Namespace": "ubuntu:14.04", + "Version": "1.42.9-3ubuntu1.3", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "sensible-utils", + "Namespace": "ubuntu:14.04", + "Version": "0.0.9", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libusb", + "Namespace": "ubuntu:14.04", + "Version": "2:0.1.12-23.3ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "shadow", + "Namespace": "ubuntu:14.04", + "Version": "1:4.1.5.1-1ubuntu9.1", + "Vulnerabilities": [ + { + "Name": "CVE-2013-4235", + "Namespace": "ubuntu:14.04", + "Description": "TOCTOU race conditions by copying and removing directory trees", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2013-4235", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "kmod", + "Namespace": "ubuntu:14.04", + "Version": "15-0ubuntu6", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ucf", + "Namespace": "ubuntu:14.04", + "Version": "3.0027+nmu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libpod-latex-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.61-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ubuntu-keyring", + "Namespace": "ubuntu:14.04", + "Version": "2012.05.19", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "perl", + "Namespace": "ubuntu:14.04", + "Version": "5.18.2-2ubuntu1", + "Vulnerabilities": [ + { + "Name": "CVE-2016-2381", + "Namespace": "ubuntu:14.04", + "Description": "environment variable confusion", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2381", + "Severity": "Medium", + "FixedBy": "5.18.2-2ubuntu1.1" + }, + { + "Name": "CVE-2013-7422", + "Namespace": "ubuntu:14.04", + "Description": "Integer underflow in regcomp.c in Perl before 5.20, as used in Apple OS X before 10.10.5 and other products, allows context-dependent attackers to execute arbitrary code or cause a denial of service (application crash) via a long digit string associated with an invalid backreference within a regular expression.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2013-7422", + "Severity": "Low", + "FixedBy": "5.18.2-2ubuntu1.1" + }, + { + "Name": "CVE-2014-4330", + "Namespace": "ubuntu:14.04", + "Description": "The Dumper method in Data::Dumper before 2.154, as used in Perl 5.20.1 and earlier, allows context-dependent attackers to cause a denial of service (stack consumption and crash) via an Array-Reference with many nested Array-References, which triggers a large number of recursive calls to the DD_dump function.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-4330", + "Severity": "Low", + "FixedBy": "5.18.2-2ubuntu1.1" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "lsb", + "Namespace": "ubuntu:14.04", + "Version": "4.1+Debian11ubuntu6", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "makedev", + "Namespace": "ubuntu:14.04", + "Version": "2.3.1-93ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libterm-ui-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.42-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "isc-dhcp", + "Namespace": "ubuntu:14.04", + "Version": "4.2.4-7ubuntu12.3", + "Vulnerabilities": [ + { + "Name": "CVE-2015-8605", + "Namespace": "ubuntu:14.04", + "Description": "ISC DHCP 4.x before 4.1-ESV-R12-P1 and 4.2.x and 4.3.x before 4.3.3-P1 allows remote attackers to cause a denial of service (application crash) via an invalid length field in a UDP IPv4 packet.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8605", + "Severity": "Medium", + "FixedBy": "4.2.4-7ubuntu12.4" + }, + { + "Name": "CVE-2016-2774", + "Namespace": "ubuntu:14.04", + "Description": "ISC DHCP 4.1.x before 4.1-ESV-R13 and 4.2.x and 4.3.x before 4.3.4 does not restrict the number of concurrent TCP sessions, which allows remote attackers to cause a denial of service (INSIST assertion failure or request-processing outage) by establishing many sessions.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2774", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 7.1, + "Vectors": "AV:N/AC:M/Au:N/C:N/I:N" + } + } + } + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "liblog-message-simple-perl", + "Namespace": "ubuntu:14.04", + "Version": "0.10-1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "plymouth", + "Namespace": "ubuntu:14.04", + "Version": "0.8.8-0ubuntu17.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "audit", + "Namespace": "ubuntu:14.04", + "Version": "1:2.3.2-2ubuntu1", + "Vulnerabilities": [ + { + "Name": "CVE-2015-5186", + "Namespace": "ubuntu:14.04", + "Description": "log terminal emulator escape sequences handling", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-5186", + "Severity": "Negligible" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libbsd", + "Namespace": "ubuntu:14.04", + "Version": "0.6.0-2ubuntu1", + "Vulnerabilities": [ + { + "Name": "CVE-2016-2090", + "Namespace": "ubuntu:14.04", + "Description": "Heap buffer overflow in fgetwln function of libbsd", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2090", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "iputils", + "Namespace": "ubuntu:14.04", + "Version": "3:20121221-4ubuntu1.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "libgpg-error", + "Namespace": "ubuntu:14.04", + "Version": "1.12-0.2ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "pam", + "Namespace": "ubuntu:14.04", + "Version": "1.1.8-1ubuntu2", + "Vulnerabilities": [ + { + "Name": "CVE-2015-3238", + "Namespace": "ubuntu:14.04", + "Description": "The _unix_run_helper_binary function in the pam_unix module in Linux-PAM (aka pam) before 1.2.1, when unable to directly access passwords, allows local users to enumerate usernames or cause a denial of service (hang) via a large password.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-3238", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 5.8, + "Vectors": "AV:N/AC:M/Au:N/C:P/I:N" + } + } + }, + "FixedBy": "1.1.8-1ubuntu2.1" + }, + { + "Name": "CVE-2013-7041", + "Namespace": "ubuntu:14.04", + "Description": "The pam_userdb module for Pam uses a case-insensitive method to compare hashed passwords, which makes it easier for attackers to guess the password via a brute force attack.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2013-7041", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 4.3, + "Vectors": "AV:N/AC:M/Au:N/C:P/I:N" + } + } + }, + "FixedBy": "1.1.8-1ubuntu2.1" + }, + { + "Name": "CVE-2014-2583", + "Namespace": "ubuntu:14.04", + "Description": "Multiple directory traversal vulnerabilities in pam_timestamp.c in the pam_timestamp module for Linux-PAM (aka pam) 1.1.8 allow local users to create aribitrary files or possibly bypass authentication via a .. (dot dot) in the (1) PAM_RUSER value to the get_ruser function or (2) PAM_TTY value to the check_tty funtion, which is used by the format_timestamp_name function.", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2014-2583", + "Severity": "Low", + "Metadata": { + "NVD": { + "CVSSv2": { + "Score": 5.8, + "Vectors": "AV:N/AC:M/Au:N/C:P/I:P" + } + } + }, + "FixedBy": "1.1.8-1ubuntu2.1" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "liblockfile", + "Namespace": "ubuntu:14.04", + "Version": "1.09-6ubuntu1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "ntp", + "Namespace": "ubuntu:14.04", + "Version": "1:4.2.6.p5+dfsg-3ubuntu2.14.04.6", + "Vulnerabilities": [ + { + "Name": "CVE-2016-0727", + "Namespace": "ubuntu:14.04", + "Description": "NTP statsdir cleanup cronjob insecure", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-0727", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8158", + "Namespace": "ubuntu:14.04", + "Description": "Potential Infinite Loop in ntpq", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8158", + "Severity": "Low" + }, + { + "Name": "CVE-2015-7973", + "Namespace": "ubuntu:14.04", + "Description": "Deja Vu: Replay attack on authenticated broadcast mode", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7973", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8140", + "Namespace": "ubuntu:14.04", + "Description": "ntpq vulnerable to replay attacks", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8140", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8139", + "Namespace": "ubuntu:14.04", + "Description": "Origin Leak: ntpq and ntpdc, disclose origin", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8139", + "Severity": "Low" + }, + { + "Name": "CVE-2015-7976", + "Namespace": "ubuntu:14.04", + "Description": "ntpq saveconfig command allows dangerous characters in filenames", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7976", + "Severity": "Low" + }, + { + "Name": "CVE-2015-7979", + "Namespace": "ubuntu:14.04", + "Description": "Off-path Denial of Service (DoS) attack on authenticated broadcast mode", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7979", + "Severity": "Low" + }, + { + "Name": "CVE-2015-8138", + "Namespace": "ubuntu:14.04", + "Description": "ntp: missing check for zero originate timestamp", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-8138", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-7977", + "Namespace": "ubuntu:14.04", + "Description": "reslist NULL pointer dereference", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7977", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-7978", + "Namespace": "ubuntu:14.04", + "Description": "Stack exhaustion in recursive traversal of restriction list", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7978", + "Severity": "Medium" + }, + { + "Name": "CVE-2015-7974", + "Namespace": "ubuntu:14.04", + "Description": "NTP 4.x before 4.2.8p6 and 4.3.x before 4.3.90 do not verify peer associations of symmetric keys when authenticating packets, which might allow remote attackers to conduct impersonation attacks via an arbitrary trusted key, aka a \"skeleton key.\"", + "Link": "http://people.ubuntu.com/~ubuntu-security/cve/CVE-2015-7974", + "Severity": "Low" + } + ], + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "newt", + "Namespace": "ubuntu:14.04", + "Version": "0.52.15-2ubuntu5", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + }, + { + "Name": "sqlite3", + "Namespace": "ubuntu:14.04", + "Version": "3.8.2-1ubuntu2.1", + "AddedBy": "sha256:d89e1bee20d9cb344674e213b581f14fbd8e70274ecf9d10c514bab78a307845" + } + ] + } + } + ] +} \ No newline at end of file diff --git a/cmd/clairctl/clair/templates.go b/cmd/clairctl/clair/templates.go new file mode 100644 index 00000000..98343d2b --- /dev/null +++ b/cmd/clairctl/clair/templates.go @@ -0,0 +1,237 @@ +// Code generated by go-bindata. +// sources: +// templates/analysis-template.html +// DO NOT EDIT! + +package clair + +import ( + "bytes" + "compress/gzip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + "time" +) + +func bindataRead(data []byte, name string) ([]byte, error) { + gz, err := gzip.NewReader(bytes.NewBuffer(data)) + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + + var buf bytes.Buffer + _, err = io.Copy(&buf, gz) + clErr := gz.Close() + + if err != nil { + return nil, fmt.Errorf("Read %q: %v", name, err) + } + if clErr != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +type asset struct { + bytes []byte + info os.FileInfo +} + +type bindataFileInfo struct { + name string + size int64 + mode os.FileMode + modTime time.Time +} + +func (fi bindataFileInfo) Name() string { + return fi.name +} +func (fi bindataFileInfo) Size() int64 { + return fi.size +} +func (fi bindataFileInfo) Mode() os.FileMode { + return fi.mode +} +func (fi bindataFileInfo) ModTime() time.Time { + return fi.modTime +} +func (fi bindataFileInfo) IsDir() bool { + return false +} +func (fi bindataFileInfo) Sys() interface{} { + return nil +} + +var _templatesAnalysisTemplateHtml = []byte("\x1f\x8b\x08\x00\x00\x09\x6e\x88\x00\xff\xdc\x5c\x7d\x6f\xe3\xb6\x19\xff\x3f\x9f\x82\x75\xae\x70\xae\x8b\xe5\xd8\xc9\xa5\xa9\xcf\xf6\xd0\xe5\x2e\xe8\x80\xac\x1d\x76\x5d\x81\xa1\x38\x14\xb4\x45\xcb\x44\x64\x51\x93\xe8\xbc\x2c\xf0\x77\xdf\x43\x52\x92\x25\x92\x92\x28\x27\xc1\x80\xe9\x9a\xb3\x23\x92\xbf\xe7\x8d\xcf\x1b\xa5\xeb\xf4\x9b\x4f\xbf\x5c\xff\xfa\xaf\xbf\x7f\x46\x6b\xbe\x09\xe7\x47\x53\xf1\x81\x42\x1c\x05\xb3\x1e\x89\x7a\xf3\x23\xb8\x43\xb0\x3f\x3f\x42\x70\x4d\x39\xe5\x21\x99\x5f\x87\x98\x26\xe8\x9a\x45\x3c\x61\x21\x4a\x48\xcc\x12\x8e\x26\xe8\xf9\xd9\xfb\xeb\x06\x07\xe4\x67\xbc\x21\xbb\xdd\x74\xa8\x26\x1f\xa9\x95\x21\x8d\xee\xd0\x3a\x21\xab\x59\x7f\xcd\x79\x9c\x4e\x86\xc3\x15\x00\xa4\x5e\xc0\x58\x10\x12\x1c\xd3\xd4\x5b\xb2\xcd\x70\x99\xa6\x7f\x5e\xe1\x0d\x0d\x9f\x66\xbf\xc4\x24\xfa\xd3\x17\x1c\xa5\x93\x8b\xb3\xb3\xd3\x4b\xf5\x43\x39\x0e\xe9\xf2\xf4\xa2\xf8\x76\x5e\xfe\xd6\x07\x6e\xc2\x59\x3f\xe5\x4f\x21\x49\xd7\x84\xf0\x3e\xe2\x4f\x31\x99\xf5\x39\x79\xe4\x02\xbb\x3f\x2f\xb1\x23\xe6\xf6\xf6\x73\x7b\x8a\xbf\x5e\xce\xdf\x06\x3f\x2e\xfd\xc8\x5b\x30\xc6\x53\x9e\xe0\x58\xfc\x22\x58\x14\x7c\x0f\xf0\x03\x49\xd9\x86\x0c\x2f\xbc\x4b\x6f\x24\x90\x2b\xb7\xbd\x0d\x85\xb9\x69\xda\xcb\xc5\x97\x54\x14\x6d\x71\x2d\x98\xff\x84\x9e\x8b\x5f\xc5\x25\x97\x2b\xc9\x27\xa8\x2f\x64\x47\x42\xf6\xfe\x29\x4a\xe1\x63\x90\x92\x84\xae\x3e\x56\x56\x6c\x70\x12\xd0\x68\x82\xce\xaa\xb7\x63\xec\xfb\x34\x0a\x8c\xfb\x0b\xbc\xbc\x0b\x12\xb6\x8d\xfc\x09\x0a\xd6\x2c\xe5\x0f\x6b\xca\x89\x75\xed\x60\xc1\x38\x67\x9b\x09\x1a\x93\xcd\x7e\xc2\xae\xf8\x36\xfc\x0e\xfd\xfa\x14\xb3\x00\x94\xb2\x7e\x42\xdf\x0d\x8b\x81\xe2\x8b\x07\x26\xf5\x6d\x12\xa6\xf4\x3f\x64\x82\x46\xde\x45\x2d\x72\x10\xb2\x05\x16\x5b\xf0\x89\x6d\xb9\x1d\x7c\x09\x48\x98\x46\x24\xd1\x28\xec\x45\x2f\x0b\xbf\xb3\x21\x00\x7f\xc9\x8a\x3e\x4e\xf0\x8a\x1b\x30\x02\x9e\x44\x7c\x82\x7a\xbd\xaa\x7a\x7c\x9a\xc6\xc0\xd7\x04\x2d\x42\xb6\xbc\xab\x8e\x49\x44\x18\x61\x7c\xdd\x4c\x3a\x61\x0f\x1a\xbd\xc2\x90\x68\x30\x3e\x8b\x1f\x5b\x97\x5b\x99\x7e\x0d\xde\x7e\x5f\x86\x38\x4d\xbf\x9b\xf5\x96\x2c\x1c\xf4\xbe\xd6\x6b\xb7\xca\xa6\xb8\x56\x21\xc3\xa0\xb1\x90\xac\xb8\xb6\xed\xd8\xa3\x30\xba\x5c\xb8\x60\x89\x4f\x12\xd8\x5d\x2d\x32\x0a\xf2\x97\x1a\xf5\x07\xea\xf3\xf5\x04\x7d\x38\xfb\xb6\x79\x6d\x8c\x23\x12\x6a\x6b\x61\x57\x15\xcc\x8f\x60\xe3\x95\x77\x95\xe2\x51\xf2\x95\x60\x9f\x6e\xd3\x09\xba\xd0\x85\x2b\xbb\x8e\xc5\x6b\xa4\x88\x6b\xec\x83\x65\x10\x28\x06\x8d\xe0\x67\x0c\x3f\xc7\x64\x2c\xfe\xb8\xf0\xbb\x1e\x5b\xf7\xc4\x80\xb3\xb8\xce\xc1\x0b\x27\xf5\x2a\x5e\x5a\x92\x27\x9f\x90\xb2\x90\xfa\x92\xab\x00\xbc\x26\x85\x51\xe6\xc0\xd3\x04\xf6\x02\x1f\x2c\xd7\x34\xd4\x1d\x39\xe3\x2d\x87\xb7\xba\x1a\xa8\xfc\x27\x08\x01\xb0\x4d\xad\x1e\x8c\xe3\x78\xb0\x56\xe3\xcf\xb5\xaa\x3e\x1e\x8f\x7e\xb8\xbc\x39\xd7\xf6\x32\x0b\x59\x62\x35\xc3\xde\x8d\x84\x11\xb2\x9f\x9a\xd8\x38\xba\x14\x36\x32\xc6\x2b\x96\x04\x6f\x14\xc6\x14\x33\x8f\xc7\x97\xe7\xe3\xf3\x2b\x0d\x8c\xa5\x94\x53\x06\x14\x21\x91\x60\x4e\xef\x49\xb3\x56\x4b\x32\xaf\x47\x75\x21\xe0\xa3\x19\x32\x1f\x08\x0d\xd6\xc2\xb7\xc4\x07\x49\xaa\x33\x44\x62\x1b\x40\x76\x8a\xd2\x15\x4b\xc0\x18\xdb\x38\x26\xc9\x12\xa7\x0e\xbc\x50\x91\xbf\xeb\x7c\xdc\xd4\x8d\xa4\x04\x89\x36\x00\x36\x97\x10\x1e\x75\x4e\x32\xc3\x58\x55\x55\xb6\x6a\x12\x2c\xf0\xc9\xf8\xc3\x87\x53\xb4\xff\xcb\xbb\x7a\xef\xb8\x83\x8f\x3f\x5f\x7f\xbe\xb9\x19\xb9\x4a\x67\xf8\x55\x29\x09\x19\x6e\xd3\xac\x6d\x1b\xa1\x74\xbb\x01\xcb\xe9\xa9\x1c\x8a\x0b\x02\x96\x56\x38\xde\x65\x6d\xa6\xcb\x2a\x27\xab\x83\x64\x63\xf5\x41\xa1\x3d\x53\xc8\xf4\x5c\x9b\x6a\xf0\x96\x33\xdd\x7f\x1e\x07\x59\x94\xfd\xe1\xd2\x30\x7f\x99\xf6\x79\x0d\x6d\x90\xe9\x8b\x28\x74\x10\x5b\x21\xbe\x26\x48\x71\x60\x95\x4f\x0d\x79\x11\xf3\x89\xbe\x05\x1b\xdc\x4a\x5c\x45\xa2\xa3\x91\x54\xb4\x25\xdf\xe5\xba\x1f\x1b\x91\x3c\x93\xcf\x1c\xc8\x15\x33\x76\xd3\xaa\x62\xdc\xf3\x99\x6e\xa2\x36\xee\x0f\x60\x4d\xcb\x4e\xe6\x84\xfa\xdc\x5b\x2e\xf9\x12\xfc\xf4\x51\xcf\x8a\xd5\x78\x97\xe7\x2d\xe9\xa1\x67\xa7\x48\xfd\xe7\x8d\x4b\xbe\xa9\x78\x29\x3b\xa4\xd5\x9d\x61\x49\xd9\xec\x2d\x7a\xf4\x3e\x91\x15\xd4\x5c\x23\x9b\x3e\xcb\x22\x2c\x42\x5c\xb6\x74\x1b\xea\x75\x02\x96\x58\x42\x29\xd9\x02\x7b\x4c\xae\x46\x64\xd4\x12\x30\xcb\xc0\x3f\x81\x09\x5b\x41\x3f\xff\x30\xfa\x7c\x79\xee\x0e\xfa\x37\x02\xe6\xdd\xb4\xc2\xde\xdc\xfc\xf8\xfd\xf8\xd2\x1d\xf6\x16\xaa\xcd\x36\xcc\xab\xbf\x5c\x9f\x5f\xfc\xe8\x8e\xf9\x33\x09\x20\x3a\xd2\x45\x68\xf5\x80\x0a\xf4\xf9\xf7\x17\xdf\x5f\xdc\xb8\x43\xff\x33\xba\x8b\xd8\x43\xf4\xda\xb8\x50\xd2\xb0\x78\x1b\xd7\x95\xcc\x11\x8b\x88\xd5\x19\xa1\xa5\x34\x4a\x88\xc2\xc1\xf1\x02\xbc\x60\x6b\x16\x83\x2a\x65\x8d\xce\xca\x95\xaa\xb8\xb4\xb2\xc9\xcc\xb0\xd9\x04\xe1\xc7\x13\x34\x18\x7d\x30\x26\xa8\x91\xf1\x2b\x95\xa7\x0d\x6e\x2e\x05\x2d\x6a\x25\x4b\x99\xd4\x5c\x2f\xcb\xc8\xa2\x07\x8a\xbc\x1a\xd6\x8b\x6f\x59\x57\xec\x83\x90\xf8\x23\xeb\x99\x18\x27\x50\x65\x74\x35\x71\x73\x43\xd7\xef\x77\x33\xa6\xcc\x76\xa6\x25\x95\x1d\x3e\xd4\x18\x38\xb7\x9f\xa1\xb6\xa6\x16\x2d\xdb\x70\x67\xf6\x6c\xa1\xb7\xf2\x4a\xff\xd9\x9a\x91\x34\xaa\xac\x77\xeb\x6d\x55\x2e\x9b\xcb\xfa\xad\xfb\x6e\x05\x91\xa7\x18\x99\x4d\x3b\x1b\x66\x41\xa0\x42\xd5\x93\xfd\xff\xb9\x65\xc4\xbe\x97\x7f\xb5\x5b\xc6\xf0\xc6\xff\x89\x95\xd0\x1c\x74\x71\xaf\x19\x89\xdd\x93\x04\x6a\x0c\x70\xcf\x35\xf5\x7d\x12\x59\xfa\x82\xfd\x14\x12\x86\x34\x4e\x69\xea\x4c\x79\xb2\x16\x8b\x6d\x11\x9f\xc5\x78\x49\x39\x58\xc5\xbb\xea\x8c\xd6\x18\xef\x2d\x56\x16\xc5\x6f\x6e\xd3\xd1\x95\x61\xab\xcc\x48\x3e\xdd\x54\xeb\xa8\x4a\xed\xbb\xc0\x49\x6a\xaf\x76\x61\x64\xb0\x08\xea\xd8\xa9\x2f\x64\x8b\x9a\xd0\x60\x28\xe7\xf5\xb2\x3e\x3f\xb9\x75\xa5\x92\x35\xdc\xe5\x34\xc9\xc9\x35\x0d\xbf\x30\xce\x0a\xc4\xa5\x1c\xb2\xe1\xa0\xd2\x28\xa3\x14\x58\xb5\x20\x6e\x2b\xd9\x33\x11\x8b\x32\xf3\x45\x15\x66\x0e\x56\x54\x97\x2f\x2c\x2c\x73\x3c\x59\x54\xbe\xb0\x9e\xcc\xb1\xb2\x5a\xf2\x85\x65\x64\x8e\x76\x6b\x1c\x58\x76\xae\x1e\x73\xa8\x52\xe5\xf8\xc2\xe2\x2e\x47\xcc\x0b\xc6\x83\xe0\xc0\x67\xef\xb7\x61\x44\x12\xbc\xa0\x21\x98\x93\xd4\xb8\x6f\x43\x33\x2e\xa2\x05\xc4\xf7\x2e\xdd\xb4\xf5\x54\x57\x51\xf0\x34\x76\x4e\xcd\x19\x2b\x82\xf9\x36\x01\x4e\xe7\x68\xab\x6f\xbe\x0e\x4f\x04\x9a\x98\x68\x22\x11\xd2\x94\xe7\x19\xa6\x5a\x36\xdb\x10\x33\x24\xdd\x3a\x2f\x3a\xdc\xc9\x30\xeb\x8f\x27\xab\x79\xf5\xf8\xfa\xe6\xd3\xd5\xa7\x6b\x27\xcc\x3f\xfe\x90\xcf\xac\xea\xce\xc4\x46\xf6\x93\x9c\x3d\x8c\xbe\x99\xac\x38\x59\x15\x32\x16\x07\x43\xe8\x1b\xba\x11\x3a\xc7\x6d\x85\x6e\x19\x59\x3f\x6e\x32\xce\x83\xaf\xf4\x83\xad\x7c\x46\x92\x9d\x3d\x78\x35\xcf\x75\xea\x25\xd9\xb7\xe8\x11\xde\x98\x45\x9c\xd4\xb4\x43\xf0\x34\x60\xf7\x3d\x7a\x3d\xae\x53\x18\x35\x90\x55\x93\xde\x80\xea\x12\x50\x0d\xd4\xbc\x4b\x6f\xc0\x75\x09\xad\x06\xae\x6c\xd3\x1b\x40\x5d\x82\xac\x01\x5a\xee\xd3\x1b\xb0\x5d\xc2\xad\x81\x5d\x34\xea\x07\x01\x43\xe0\x85\xea\x82\xd4\x95\x4b\x72\x2c\xfb\xb0\xfb\xe4\x72\x9b\xa4\x82\x46\xcc\xa8\x79\x12\x6d\x77\x58\x71\xd5\x47\x1e\x3d\x4a\x34\xc9\x21\x2e\x4b\xa8\xb5\xe9\xcd\x22\x49\x56\xa1\x36\xa5\x7a\x87\x28\x28\x11\xbd\x65\xc8\x52\xe2\x97\x22\xb6\xd3\x01\x47\xc3\x49\xf6\x40\x54\xf3\x75\x28\xab\x90\x18\x27\x16\xfb\x24\x67\xd6\xa8\xcd\x87\xcd\x95\xc3\x10\xc3\x52\x95\xe3\xe6\xb6\x68\x55\x61\xde\x76\xa6\xdc\xf6\xec\x42\x48\x06\x4c\x74\xa5\xd2\xb1\xa7\xed\x70\x72\x6d\x76\x8b\xa5\x66\xbf\xb1\x1a\x36\x7a\xdf\xd6\x27\x6a\x99\xaa\x13\x1b\x65\x37\x3d\xe4\xd9\xc1\xae\x8f\x8e\x05\xb6\x05\x3e\xcf\x12\xed\xf8\x4e\xb9\xc2\x42\x41\x64\x0b\x07\x74\x97\x9c\x61\x41\x57\x59\xc3\x01\xdf\x25\x77\x58\xf0\x21\x7b\x38\x80\xbb\xe4\x10\x0b\xf8\x3e\x8b\x38\xd0\x70\xc9\x25\x16\x1a\x59\x36\x79\x25\x02\x79\xe3\x3b\xb0\x3d\x01\x7b\x93\x68\xd6\xea\x63\x82\xd8\xc0\xa7\x09\x59\x66\xbd\x39\x7b\x18\x24\x04\xf2\x40\x6a\x1c\xd6\x56\xbc\xf9\x5c\xe7\xa2\xfe\x2c\xc6\x41\x13\x87\x84\xc6\x3c\x22\x5d\x35\x9d\x3a\x1b\x32\xbb\x32\xf3\x3a\x3d\xb9\x15\xf9\xb5\x1a\x74\x2b\xf8\x6b\x74\xeb\x56\xe0\xd7\x69\xdd\xad\xd0\xaf\xd0\xc7\x5b\x71\x5f\xaf\xa9\xb7\xc2\x1f\xdc\xe1\x4f\x87\xd9\x5b\x77\xd3\xa1\x7a\x85\xf1\x68\x2a\xde\xbb\xcb\xde\x00\x14\xc7\x9c\xf2\x75\x27\xf1\xb6\x53\xf6\x3a\x59\x6f\xff\x86\xde\x34\x7b\x53\x23\x9b\xb2\x7f\x77\xa3\x34\x47\xcd\x1b\x59\xdf\x87\x04\x9a\xa3\x12\xda\x50\x2d\xce\x5e\x07\xd4\x19\xd8\xbf\xaf\x90\xbf\x97\x66\x50\x19\xcf\xe5\xbb\x95\xe6\x5b\x96\x30\x52\x99\xfa\xfc\x8c\xde\x51\x8c\x26\x33\xe4\xed\x76\x47\x55\x94\x54\x85\x9f\x9c\x6c\x16\x88\x35\x5a\x39\x73\xe6\x5d\x05\xff\x40\xf9\x1a\xbd\xd3\x1a\x82\x6b\xb0\x04\x17\x44\x71\x18\xfe\xa6\xf5\x0a\xc0\xce\x6e\x67\x05\x9b\xc6\xf3\x69\x1a\xe3\x82\x21\xf1\xd2\x60\x0f\x6e\x81\x26\xa2\x60\xfe\x2b\xe3\xe0\xbd\x42\x62\x2b\x35\x4f\x8e\xef\x76\xfa\x11\x8e\x30\xbb\x5c\x0f\x5f\x00\x1b\x3e\x62\xbb\x28\xf5\x03\x25\xd3\x94\x93\x95\x45\x51\x7b\xb5\xd0\x15\x0a\x38\x3a\xb1\xb3\xaa\xd4\xd3\xcb\xb6\x72\xef\x3d\x3a\xab\xd1\x88\x4e\x5e\x06\xec\x7c\xd9\x3c\x77\x85\x09\xca\x75\x54\xa7\x1b\x8d\xa0\xd8\x28\x85\x56\x6a\x6d\xab\x04\x21\x91\x0f\x4a\x75\x13\x68\xef\xfa\x5d\x65\x2a\xad\x9c\x97\x02\x88\xb3\x64\xa5\xf5\x6f\x25\x1c\xc4\xcb\xae\x52\x89\x25\x73\x11\x67\x9d\xe5\x10\x2b\xde\x4a\x00\x95\x4b\xba\xca\x90\xad\x9a\x67\x99\xc8\x59\x92\x6c\xdd\x5b\x09\x23\x32\x6e\x57\x51\xe4\x9a\xb9\xcc\xd5\xce\x62\xc8\x35\x6f\x25\x44\x5e\x93\x74\x15\xa4\x58\x37\x2f\xaa\x1a\x67\x81\x8a\xb5\x6f\x25\x54\x56\xc2\x75\x95\x29\x5f\x36\xcf\x4b\x40\x67\x89\xf2\x95\x9d\x05\xaa\x49\x03\xb5\x4b\xcb\x3c\x57\x6b\x93\x86\x54\x50\x2b\x28\x92\xe5\xc8\xac\x97\xb5\x18\x75\x42\xfe\x23\x23\x64\x08\xfb\x6d\xaf\x4d\xcc\xfa\x7d\x73\x18\xed\xd2\xd6\x39\x80\xb8\xf4\xa4\xc3\x08\x67\x4e\x78\x00\xd1\x2c\x0a\x1d\x46\xb6\x08\x61\x07\x10\x16\x81\xfc\x30\xaa\x2a\x05\x1c\x40\xb2\x94\x03\x0f\xa3\x5c\x49\xa2\x07\x30\x90\x97\x17\x87\x51\xdf\x17\x27\xcd\xa4\x1b\x86\xea\xfc\xda\xb2\x04\x42\x85\xaa\x7e\xab\xb7\x8f\xea\x04\xac\x73\xf3\xe7\xe7\x04\x47\x01\x41\xef\xee\x4e\xdf\xdd\x8b\x8a\x57\x3f\x1a\xb7\x97\xbb\xc5\xb2\x7b\x1b\xbb\xb8\xa2\x56\x28\xf1\xbf\x88\xb3\x01\xca\x9f\x76\xbb\xfc\x5f\x25\x1d\x43\x61\xef\x89\xa2\x1f\xc1\xbd\xf6\x70\xe5\x33\xde\xac\xd4\xd2\x5c\xf9\xae\x44\x4b\x44\x9b\xef\x63\xb3\x97\xb7\x1e\x6e\xb1\x57\xae\xae\x88\xe4\xb8\xe0\x46\x1d\x6a\x37\xce\xaf\x19\x9a\x0e\xb1\xcd\x72\xf6\xcd\x62\xbb\xaf\xe1\x66\xbf\xee\x7f\xd7\x3a\x29\xd5\xf3\xe9\x4d\x9b\x9d\xb5\xb2\xe2\xc5\xbf\xb3\x70\x30\xa6\x7a\x56\xd2\x96\x73\xa8\x3f\xeb\x81\xda\x6e\x71\xca\x6f\xe5\xd3\x01\x65\xa7\x5e\x05\xa5\x01\x44\x02\xad\xcf\x2b\xd3\xb3\x67\x16\x3d\xe4\x63\x8e\x07\x9c\x05\x41\x48\x06\x72\xc4\x4e\x6b\x6e\xb9\x09\x3d\xea\x79\x0b\xd5\x92\xac\xf9\xa3\x8c\x16\x46\xe5\xb2\x6d\xd8\x3e\x49\x5c\xe0\x3a\xca\xfb\x52\xb0\x12\xf1\xdd\x1b\x54\x83\x62\x48\x35\x3e\x1d\xd8\x2c\x16\x9b\x52\xe6\xea\x75\x07\x41\xa8\x54\x25\x15\x01\xa1\xf0\x45\x24\xfb\x69\x39\xf4\x1b\x6c\x19\xb1\x45\xe5\xa8\xb8\x89\x06\xa8\xd2\x6d\xaf\x30\x5a\xe1\x01\x79\x84\x5f\x37\x58\xec\xe6\x01\x4f\x28\xa8\x49\x98\x1b\xc3\xb7\x81\x3a\x61\x9c\xf5\x78\xb2\x25\xbd\xbc\x9d\x76\x17\xb7\xd9\xcb\xf5\xab\xb0\x91\xa7\x99\xc7\xd1\x34\x92\xe4\x36\xcc\x85\xd3\xa2\x72\x37\x0d\x97\xed\xac\x3d\x79\x07\xc5\xe6\x81\xac\x3e\x10\x37\x42\x63\x2b\x72\xe1\x68\xe2\x79\xaa\xf0\xad\x52\xb0\xb7\x46\xb3\x56\x3a\x6a\x47\x14\x89\x05\xc0\x7a\xd6\x2d\xd3\x1d\x59\xc5\x67\xf1\x4e\x40\xba\x4c\x68\xcc\xf3\x5d\xd6\xc9\xde\x05\x1a\xce\x12\x9c\x40\xbc\x15\xff\x2c\x57\x84\x2c\x8e\x93\x80\xf0\x59\x6f\x11\xe2\xe8\x0e\x5a\x6a\xb8\xdf\x59\x0b\xd3\x61\x48\xbb\x6c\x57\xd7\x68\xe2\x8e\xdb\xd4\x76\x1c\x3a\xb7\x9d\xcf\xb6\xf4\x7a\x40\x36\xb5\xdf\x5e\x24\x68\xa8\x17\x59\xc5\xcc\xca\xd7\x4a\xe9\x55\x4e\xa6\x53\xb5\x83\xf6\x28\x27\xab\x6d\x24\xe7\x9e\xbc\x37\x1f\xab\xa6\x1c\xa9\x14\x94\xa4\x68\x86\x7c\xb6\xdc\x6e\x08\xd4\x95\xff\xde\x92\xe4\xe9\x0b\x09\x81\x08\x4b\x7e\x0c\xc3\x93\xfe\xef\x46\xba\xfa\xda\x7f\xaf\x3f\xdc\x8f\x52\x16\x12\x2f\x64\xc1\x49\x0e\x0a\x53\x2a\x73\x56\x2c\x41\x27\xf7\x38\x41\x14\xc8\xe5\x93\xbc\x90\x44\x01\x5f\x43\x34\x1d\x7d\x84\x81\xf9\x0c\x9d\xc1\xe7\x60\xa0\xf3\x2b\xae\x7c\xcd\xef\xf4\xab\xc7\xa2\x65\x48\x97\x77\x80\x54\x88\x48\x6c\x6b\xc4\x45\x3c\xe5\x00\x9e\x7a\xcd\xf7\x67\x71\x0e\x2e\x1d\xf9\x96\xa6\xdc\x53\xa8\x27\x7d\xf5\xf4\x5f\x17\x4c\x5c\xbb\xea\xad\xfd\xae\xda\xbd\x3f\xc9\xa6\x83\x51\x32\xd5\x4f\x87\xea\x58\x5c\x9c\x93\x8b\xff\x07\xc0\x7f\x03\x00\x00\xff\xff\xb3\xfc\x2e\xd8\x13\x40\x00\x00") + +func templatesAnalysisTemplateHtmlBytes() ([]byte, error) { + return bindataRead( + _templatesAnalysisTemplateHtml, + "templates/analysis-template.html", + ) +} + +func templatesAnalysisTemplateHtml() (*asset, error) { + bytes, err := templatesAnalysisTemplateHtmlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "templates/analysis-template.html", size: 16403, mode: os.FileMode(420), modTime: time.Unix(1466557241, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +// Asset loads and returns the asset for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func Asset(name string) ([]byte, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) + } + return a.bytes, nil + } + return nil, fmt.Errorf("Asset %s not found", name) +} + +// MustAsset is like Asset but panics when Asset would return an error. +// It simplifies safe initialization of global variables. +func MustAsset(name string) []byte { + a, err := Asset(name) + if err != nil { + panic("asset: Asset(" + name + "): " + err.Error()) + } + + return a +} + +// AssetInfo loads and returns the asset info for the given name. +// It returns an error if the asset could not be found or +// could not be loaded. +func AssetInfo(name string) (os.FileInfo, error) { + cannonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[cannonicalName]; ok { + a, err := f() + if err != nil { + return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) + } + return a.info, nil + } + return nil, fmt.Errorf("AssetInfo %s not found", name) +} + +// AssetNames returns the names of the assets. +func AssetNames() []string { + names := make([]string, 0, len(_bindata)) + for name := range _bindata { + names = append(names, name) + } + return names +} + +// _bindata is a table, holding each asset generator, mapped to its name. +var _bindata = map[string]func() (*asset, error){ + "templates/analysis-template.html": templatesAnalysisTemplateHtml, +} + +// AssetDir returns the file names below a certain +// directory embedded in the file by go-bindata. +// For example if you run go-bindata on data/... and data contains the +// following hierarchy: +// data/ +// foo.txt +// img/ +// a.png +// b.png +// then AssetDir("data") would return []string{"foo.txt", "img"} +// AssetDir("data/img") would return []string{"a.png", "b.png"} +// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// AssetDir("") will return []string{"data"}. +func AssetDir(name string) ([]string, error) { + node := _bintree + if len(name) != 0 { + cannonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(cannonicalName, "/") + for _, p := range pathList { + node = node.Children[p] + if node == nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + } + } + if node.Func != nil { + return nil, fmt.Errorf("Asset %s not found", name) + } + rv := make([]string, 0, len(node.Children)) + for childName := range node.Children { + rv = append(rv, childName) + } + return rv, nil +} + +type bintree struct { + Func func() (*asset, error) + Children map[string]*bintree +} +var _bintree = &bintree{nil, map[string]*bintree{ + "templates": &bintree{nil, map[string]*bintree{ + "analysis-template.html": &bintree{templatesAnalysisTemplateHtml, map[string]*bintree{}}, + }}, +}} + +// RestoreAsset restores an asset under the given directory +func RestoreAsset(dir, name string) error { + data, err := Asset(name) + if err != nil { + return err + } + info, err := AssetInfo(name) + if err != nil { + return err + } + err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755)) + if err != nil { + return err + } + err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode()) + if err != nil { + return err + } + err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) + if err != nil { + return err + } + return nil +} + +// RestoreAssets restores an asset under the given directory recursively +func RestoreAssets(dir, name string) error { + children, err := AssetDir(name) + // File + if err != nil { + return RestoreAsset(dir, name) + } + // Dir + for _, child := range children { + err = RestoreAssets(dir, filepath.Join(name, child)) + if err != nil { + return err + } + } + return nil +} + +func _filePath(dir, name string) string { + cannonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) +} + diff --git a/cmd/clairctl/clair/templates/analysis-template.html b/cmd/clairctl/clair/templates/analysis-template.html new file mode 100644 index 00000000..15015b21 --- /dev/null +++ b/cmd/clairctl/clair/templates/analysis-template.html @@ -0,0 +1,558 @@ + + + + + Clair Control report : {{.ImageName}} + + + + + + + + +
+
+

Clair Control report

+
+ +
+

Image: {{.ImageName}}

+ {{ $ia := .}} + +
+
+ {{with $vulnerabilitiesCount := allVulnerabilities $ia}} +

Total : {{$vulnerabilitiesCount.Total}} vulnerabilities

+

+
+ {{if gt ($vulnerabilitiesCount.Count "Unknown") 0}} +
Unknown : {{$vulnerabilitiesCount.Count "Unknown"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "Negligible") 0}} +
Negligible : {{$vulnerabilitiesCount.Count "Negligible"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "Low") 0}} +
Low : {{$vulnerabilitiesCount.Count "Low"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "Medium") 0}} +
Medium : {{$vulnerabilitiesCount.Count "Medium"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "High") 0}} +
High : {{$vulnerabilitiesCount.Count "High"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "Critical") 0}} +
Critical : {{$vulnerabilitiesCount.Count "Critical"}}
+ {{end}} {{if gt ($vulnerabilitiesCount.Count "Defcon1") 0}} +
Defcon1 : {{$vulnerabilitiesCount.Count "Defcon1"}}
+ {{end}} +
+
+
+
+
+
+
+
+
+
+ {{end}} +
+
+ +
+ {{range $k,$v := vulnerabilities $ia}} + {{range $v}} + +
+ +
+ {{end}} + {{end}} +
+
+ +
+
+
+
+
+

{{.LastLayer.Name}}

+
+
    + {{ range sortedVulnerabilities $ia}} +
  • +
    + {{ .Name }} {{ .Version }} - +
    + {{ range .Vulnerabilities}} +
      +
    • + + {{ .Name }} +
      {{ .Description }}
      + Link +
    • +
    +
  • + {{end}} + {{end}} +
+
+
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/cmd/clairctl/clair/versions.go b/cmd/clairctl/clair/versions.go new file mode 100644 index 00000000..f0d4ba5e --- /dev/null +++ b/cmd/clairctl/clair/versions.go @@ -0,0 +1,23 @@ +package clair + +import ( + "encoding/json" + "fmt" + "net/http" +) + +func Versions() (interface{}, error) { + Config() + response, err := http.Get(uri + "/versions") + if err != nil { + return nil, fmt.Errorf("requesting Clair version: %v", err) + } + defer response.Body.Close() + + var versionBody interface{} + err = json.NewDecoder(response.Body).Decode(&versionBody) + if err != nil { + return nil, fmt.Errorf("reading Clair version body: %v", err) + } + return versionBody, nil +} diff --git a/cmd/clairctl/clairctl.yml.default b/cmd/clairctl/clairctl.yml.default new file mode 100644 index 00000000..87df3b3a --- /dev/null +++ b/cmd/clairctl/clairctl.yml.default @@ -0,0 +1,10 @@ +clair: + port: 6060 + healthPort: 6061 + uri: http://clair + report: + path: ./reports + format: html +docker: + insecure-registries: + - "my-own-registry:5000" diff --git a/cmd/clairctl/cmd/analyze.go b/cmd/clairctl/cmd/analyze.go new file mode 100644 index 00000000..48f71062 --- /dev/null +++ b/cmd/clairctl/cmd/analyze.go @@ -0,0 +1,60 @@ +package cmd + +import ( + "fmt" + "os" + "text/template" + + "github.com/jgsqware/clairctl/clair" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker" + "github.com/spf13/cobra" +) + +const analyzeTplt = ` +Image: {{.String}} + {{.Layers | len}} layers found + {{$ia := .}} + {{range .Layers}} ➜ {{with .Layer}}Analysis [{{.|$ia.ShortName}}] found {{.|$ia.CountVulnerabilities}} vulnerabilities.{{end}} + {{end}} +` + +var analyzeCmd = &cobra.Command{ + Use: "analyze IMAGE", + Short: "Analyze Docker image", + Long: `Analyze a Docker image with Clair, against Ubuntu, Red hat and Debian vulnerabilities databases`, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) != 1 { + fmt.Printf("clairctl: \"analyze\" requires a minimum of 1 argument") + os.Exit(1) + } + + config.ImageName = args[0] + image, manifest, err := docker.RetrieveManifest(config.ImageName, true) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving manifest for %q: %v", config.ImageName, err) + } + + startLocalServer() + if err := clair.Push(image, manifest); err != nil { + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("pushing image %q: %v", image.String(), err) + } + } + + analysis := clair.Analyze(image, manifest) + err = template.Must(template.New("analysis").Parse(analyzeTplt)).Execute(os.Stdout, analysis) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("rendering analysis: %v", err) + } + }, +} + +func init() { + RootCmd.AddCommand(analyzeCmd) + analyzeCmd.Flags().BoolVarP(&config.IsLocal, "local", "l", false, "Use local images") +} diff --git a/cmd/clairctl/cmd/health.go b/cmd/clairctl/cmd/health.go new file mode 100644 index 00000000..60c7f44e --- /dev/null +++ b/cmd/clairctl/cmd/health.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "fmt" + "os" + "text/template" + + "github.com/jgsqware/clairctl/clair" + "github.com/spf13/cobra" +) + +const healthTplt = ` +Clair: {{if .}}✔{{else}}✘{{end}} +` + +type health struct { + Clair interface{} `json:"clair"` +} + +var healthCmd = &cobra.Command{ + Use: "health", + Short: "Get Health of clairctl and underlying services", + Long: `Get Health of clairctl and underlying services`, + Run: func(cmd *cobra.Command, args []string) { + ok := clair.IsHealthy() + err := template.Must(template.New("health").Parse(healthTplt)).Execute(os.Stdout, ok) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("rendering the health: %v", err) + } + + }, +} + +func init() { + RootCmd.AddCommand(healthCmd) +} diff --git a/cmd/clairctl/cmd/pull.go b/cmd/clairctl/cmd/pull.go new file mode 100644 index 00000000..0b92c874 --- /dev/null +++ b/cmd/clairctl/cmd/pull.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "fmt" + "html/template" + "os" + + "github.com/docker/docker/reference" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker" + "github.com/opencontainers/go-digest" + "github.com/spf13/cobra" +) + +const pullTplt = ` +Image: {{.Named.FullName}}:{{.Named.Tag}} + {{.Layers | len}} layers found + {{range .Layers}} ➜ {{.}} + {{end}} +` + +var pullCmd = &cobra.Command{ + Use: "pull IMAGE", + Short: "Pull Docker image to Clair", + Long: `Upload a Docker image to Clair for further analysis`, + Run: func(cmd *cobra.Command, args []string) { + + if len(args) != 1 { + fmt.Printf("clairctl: \"pull\" requires a minimum of 1 argument\n") + os.Exit(1) + } + + config.ImageName = args[0] + image, manifest, err := docker.RetrieveManifest(config.ImageName, true) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving manifest for %q: %v", config.ImageName, err) + } + + layers, err := docker.GetLayerDigests(manifest) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving layers for %q: %v", config.ImageName, err) + } + data := struct { + Layers []digest.Digest + Named reference.Named + }{ + Layers: layers, + Named: image, + } + + err = template.Must(template.New("pull").Parse(pullTplt)).Execute(os.Stdout, data) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("rendering image: %v", err) + } + }, +} + +func init() { + RootCmd.AddCommand(pullCmd) + pullCmd.Flags().BoolVarP(&config.Insecure, "insecure", "i", false, "use an insecure registry") + pullCmd.Flags().BoolVarP(&config.IsLocal, "local", "l", false, "Use local images") +} diff --git a/cmd/clairctl/cmd/push.go b/cmd/clairctl/cmd/push.go new file mode 100644 index 00000000..b85d38f5 --- /dev/null +++ b/cmd/clairctl/cmd/push.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "fmt" + "os" + + "github.com/jgsqware/clairctl/clair" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker" + "github.com/jgsqware/clairctl/server" + "github.com/spf13/cobra" +) + +var pushCmd = &cobra.Command{ + Use: "push IMAGE", + Short: "Push Docker image to Clair", + Long: `Upload a Docker image to Clair for further analysis`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Printf("clairctl: \"push\" requires a minimum of 1 argument\n") + os.Exit(1) + } + + startLocalServer() + config.ImageName = args[0] + image, manifest, err := docker.RetrieveManifest(config.ImageName, true) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving manifest for %q: %v", config.ImageName, err) + } + + if err := clair.Push(image, manifest); err != nil { + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("pushing image %q: %v", image.String(), err) + } + } + fmt.Printf("%v has been pushed to Clair\n", image.String()) + }, +} + +func startLocalServer() { + sURL, err := config.LocalServerIP() + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving internal server IP: %v", err) + } + err = server.Serve(sURL) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("starting local server: %v", err) + } +} + +func init() { + RootCmd.AddCommand(pushCmd) + pushCmd.Flags().BoolVarP(&config.IsLocal, "local", "l", false, "Use local images") +} diff --git a/cmd/clairctl/cmd/report.go b/cmd/clairctl/cmd/report.go new file mode 100644 index 00000000..c5fa6e7e --- /dev/null +++ b/cmd/clairctl/cmd/report.go @@ -0,0 +1,99 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/jgsqware/clairctl/clair" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker" + "github.com/jgsqware/clairctl/xstrings" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var reportCmd = &cobra.Command{ + Use: "report IMAGE", + Short: "Generate Docker Image vulnerabilities report", + Long: `Generate Docker Image vulnerabilities report as HTML or JSON`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + fmt.Printf("clairctl: \"report\" requires a minimum of 1 argument") + os.Exit(1) + } + + config.ImageName = args[0] + image, manifest, err := docker.RetrieveManifest(config.ImageName, true) + + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("retrieving manifest for %q: %v", config.ImageName, err) + } + + analyzes := clair.Analyze(image, manifest) + imageName := strings.Replace(analyzes.ImageName, "/", "-", -1) + if analyzes.Tag != "" { + imageName += "-" + analyzes.Tag + } + + switch clair.Report.Format { + case "html": + html, err := clair.ReportAsHTML(analyzes) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("generating HTML report: %v", err) + } + err = saveReport(imageName, string(html)) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("saving HTML report: %v", err) + } + + case "json": + json, err := xstrings.ToIndentJSON(analyzes) + + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("indenting JSON: %v", err) + } + err = saveReport(imageName, string(json)) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("saving JSON report: %v", err) + } + + default: + fmt.Printf("Unsupported Report format: %v", clair.Report.Format) + log.Fatalf("Unsupported Report format: %v", clair.Report.Format) + } + }, +} + +func saveReport(name string, content string) error { + path := viper.GetString("clair.report.path") + "/" + clair.Report.Format + if err := os.MkdirAll(path, 0777); err != nil { + return err + } + + reportsName := fmt.Sprintf("%v/analysis-%v.%v", path, name, strings.ToLower(clair.Report.Format)) + f, err := os.Create(reportsName) + if err != nil { + return fmt.Errorf("creating report file: %v", err) + } + + _, err = f.WriteString(content) + + if err != nil { + return fmt.Errorf("writing report file: %v", err) + } + fmt.Printf("%v report at %v\n", strings.ToUpper(clair.Report.Format), reportsName) + return nil +} + +func init() { + RootCmd.AddCommand(reportCmd) + reportCmd.Flags().BoolVarP(&config.IsLocal, "local", "l", false, "Use local images") + reportCmd.Flags().StringP("format", "f", "html", "Format for Report [html,json]") + viper.BindPFlag("clair.report.format", reportCmd.Flags().Lookup("format")) +} diff --git a/cmd/clairctl/cmd/root.go b/cmd/clairctl/cmd/root.go new file mode 100644 index 00000000..b7475d2e --- /dev/null +++ b/cmd/clairctl/cmd/root.go @@ -0,0 +1,54 @@ +package cmd + +import ( + "errors" + "fmt" + "os" + + "github.com/coreos/pkg/capnslog" + "github.com/jgsqware/clairctl/clair" + "github.com/jgsqware/clairctl/config" + "github.com/spf13/cobra" +) + +var errInternalError = errors.New("client quit unexpectedly") + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "cmd") + +var cfgFile string +var logLevel string +var noClean bool + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "clairctl", + Short: "Analyze your docker image with Clair, directly from your registry or local images.", + Long: ``, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + err = config.Clean() + fmt.Println(err) + os.Exit(-1) + } + + if err := config.Clean(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.clairctl.yml)") + RootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "", "log level [Panic,Fatal,Error,Warn,Info,Debug]") + RootCmd.PersistentFlags().BoolVar(&noClean, "no-clean", false, "Disable the temporary folder cleaning") +} + +func initConfig() { + config.Init(cfgFile, logLevel, noClean) + clair.Config() +} diff --git a/cmd/clairctl/cmd/version.go b/cmd/clairctl/cmd/version.go new file mode 100644 index 00000000..d689fb94 --- /dev/null +++ b/cmd/clairctl/cmd/version.go @@ -0,0 +1,35 @@ +package cmd + +import ( + "fmt" + "os" + "text/template" + + "github.com/spf13/cobra" +) + +const versionTplt = ` +Clairctl version {{.}} +` + +var version string + +var templ = template.Must(template.New("versions").Parse(versionTplt)) + +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get Versions of Clairctl and underlying services", + Long: `Get Versions of Clairctl and underlying services`, + Run: func(cmd *cobra.Command, args []string) { + + err := templ.Execute(os.Stdout, version) + if err != nil { + fmt.Println(errInternalError) + log.Fatalf("rendering the version: %v", err) + } + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/cmd/clairctl/config/config.go b/cmd/clairctl/config/config.go new file mode 100644 index 00000000..fc504286 --- /dev/null +++ b/cmd/clairctl/config/config.go @@ -0,0 +1,342 @@ +package config + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net" + "os" + "os/user" + "strings" + + "gopkg.in/yaml.v2" + + "github.com/coreos/pkg/capnslog" + "github.com/jgsqware/clairctl/xstrings" + "github.com/jgsqware/xnet" + "github.com/spf13/viper" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "config") + +var errNoInterfaceProvided = errors.New("could not load configuration: no interface provided") +var errInvalidInterface = errors.New("Interface does not exist") +var ErrLoginNotFound = errors.New("user is not log in") + +var IsLocal = false +var Insecure = false +var NoClean = false + +var ImageName string + +type reportConfig struct { + Path, Format string +} +type clairConfig struct { + URI string + Port, HealthPort int + Report reportConfig +} +type authConfig struct { + InsecureSkipVerify bool +} +type clairctlConfig struct { + IP, Interface, TempFolder string + Port int +} +type docker struct { + InsecureRegistries []string +} + +type config struct { + Clair clairConfig + Auth authConfig + Clairctl clairctlConfig + Docker docker +} + +// Init reads in config file and ENV variables if set. +func Init(cfgFile string, logLevel string, noClean bool) { + + NoClean = noClean + + lvl := capnslog.WARNING + if logLevel != "" { + // Initialize logging system + var err error + lvl, err = capnslog.ParseLevel(strings.ToUpper(logLevel)) + if err != nil { + log.Warningf("Wrong Log level %v, defaults to [Warning]", logLevel) + lvl = capnslog.WARNING + } + + } + capnslog.SetGlobalLogLevel(lvl) + capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, false)) + + viper.SetEnvPrefix("clairctl") + viper.SetConfigName("clairctl") // name of config file (without extension) + viper.AddConfigPath("$HOME/.clairctl") // adding home directory as first search path + viper.AddConfigPath(".") // adding home directory as first search path + viper.AutomaticEnv() // read in environment variables that match + if cfgFile != "" { + viper.SetConfigFile(cfgFile) + } + err := viper.ReadInConfig() + if err != nil { + log.Debugf("No config file used") + } else { + log.Debugf("Using config file: %v", viper.ConfigFileUsed()) + } + + if viper.Get("clair.uri") == nil { + viper.Set("clair.uri", "http://localhost") + } + if viper.Get("clair.port") == nil { + viper.Set("clair.port", "6060") + } + if viper.Get("clair.healthPort") == nil { + viper.Set("clair.healthPort", "6061") + } + + if viper.Get("clair.report.path") == nil { + viper.Set("clair.report.path", "reports") + } + if viper.Get("clair.report.format") == nil { + viper.Set("clair.report.format", "html") + } + if viper.Get("auth.insecureSkipVerify") == nil { + viper.Set("auth.insecureSkipVerify", "true") + } + if viper.Get("clairctl.ip") == nil { + viper.Set("clairctl.ip", "") + } + if viper.Get("clairctl.port") == nil { + viper.Set("clairctl.port", 0) + } + if viper.Get("clairctl.interface") == nil { + viper.Set("clairctl.interface", "") + } + if viper.Get("clairctl.tempFolder") == nil { + viper.Set("clairctl.tempFolder", "/tmp/clairctl") + } + +} + +func TmpLocal() string { + return viper.GetString("clairctl.tempFolder") +} + +func values() config { + return config{ + Clair: clairConfig{ + URI: viper.GetString("clair.uri"), + Port: viper.GetInt("clair.port"), + HealthPort: viper.GetInt("clair.healthPort"), + Report: reportConfig{ + Path: viper.GetString("clair.report.path"), + Format: viper.GetString("clair.report.format"), + }, + }, + Auth: authConfig{ + InsecureSkipVerify: viper.GetBool("auth.insecureSkipVerify"), + }, + Clairctl: clairctlConfig{ + IP: viper.GetString("clairctl.ip"), + Port: viper.GetInt("clairctl.port"), + TempFolder: viper.GetString("clairctl.tempFolder"), + Interface: viper.GetString("clairctl.interface"), + }, + Docker: docker{ + InsecureRegistries: viper.GetStringSlice("docker.insecure-registries"), + }, + } +} + +func Print() { + cfg := values() + cfgBytes, err := yaml.Marshal(cfg) + if err != nil { + log.Fatalf("marshalling configuration: %v", err) + } + + fmt.Println("Configuration") + fmt.Printf("%v", string(cfgBytes)) +} + +func ClairctlHome() string { + usr, err := user.Current() + if err != nil { + panic(err) + } + p := usr.HomeDir + "/.clairctl" + + if _, err := os.Stat(p); os.IsNotExist(err) { + os.Mkdir(p, 0700) + } + return p +} + +type Login struct { + Username string + Password string +} + +type loginMapping map[string]Login + +func ClairctlConfig() string { + return ClairctlHome() + "/config.json" +} + +func AddLogin(registry string, login Login) error { + var logins loginMapping + + if err := readConfigFile(&logins, ClairctlConfig()); err != nil { + return fmt.Errorf("reading clairctl file: %v", err) + } + + logins[registry] = login + + if err := writeConfigFile(logins, ClairctlConfig()); err != nil { + return fmt.Errorf("indenting login: %v", err) + } + + return nil +} +func GetLogin(registry string) (Login, error) { + if _, err := os.Stat(ClairctlConfig()); err == nil { + var logins loginMapping + + if err := readConfigFile(&logins, ClairctlConfig()); err != nil { + return Login{}, fmt.Errorf("reading clairctl file: %v", err) + } + + if login, present := logins[registry]; present { + d, err := base64.StdEncoding.DecodeString(login.Password) + if err != nil { + return Login{}, fmt.Errorf("decoding password: %v", err) + } + login.Password = string(d) + return login, nil + } + } + return Login{}, ErrLoginNotFound +} + +func RemoveLogin(registry string) (bool, error) { + if _, err := os.Stat(ClairctlConfig()); err == nil { + var logins loginMapping + + if err := readConfigFile(&logins, ClairctlConfig()); err != nil { + return false, fmt.Errorf("reading clairctl file: %v", err) + } + + if _, present := logins[registry]; present { + delete(logins, registry) + + if err := writeConfigFile(logins, ClairctlConfig()); err != nil { + return false, fmt.Errorf("indenting login: %v", err) + } + + return true, nil + } + } + return false, nil +} + +func readConfigFile(logins *loginMapping, file string) error { + if _, err := os.Stat(file); err == nil { + f, err := ioutil.ReadFile(file) + if err != nil { + return err + } + + if err := json.Unmarshal(f, &logins); err != nil { + return err + } + } else { + *logins = loginMapping{} + } + return nil +} + +func writeConfigFile(logins loginMapping, file string) error { + s, err := xstrings.ToIndentJSON(logins) + if err != nil { + return err + } + err = ioutil.WriteFile(file, s, os.ModePerm) + if err != nil { + return err + } + return nil +} + +//LocalServerIP return the local clairctl server IP +func LocalServerIP() (string, error) { + localPort := viper.GetString("clairctl.port") + localIP := viper.GetString("clairctl.ip") + localInterfaceConfig := viper.GetString("clairctl.interface") + + if localIP == "" { + log.Info("retrieving interface for local IP") + var err error + var localInterface net.Interface + localInterface, err = translateInterface(localInterfaceConfig) + if err != nil { + return "", fmt.Errorf("retrieving interface: %v", err) + } + + localIP, err = xnet.IPv4(localInterface) + if err != nil { + return "", fmt.Errorf("retrieving interface ip: %v", err) + } + } + return strings.TrimSpace(localIP) + ":" + localPort, nil +} + +func translateInterface(localInterface string) (net.Interface, error) { + + if localInterface != "" { + log.Debug("interface provided, looking for " + localInterface) + netInterface, err := net.InterfaceByName(localInterface) + if err != nil { + return net.Interface{}, err + } + return *netInterface, nil + } + + log.Debug("no interface provided, looking for docker0") + netInterface, err := net.InterfaceByName("docker0") + if err != nil { + log.Debug("docker0 not found, looking for first connected broadcast interface") + interfaces, err := net.Interfaces() + if err != nil { + return net.Interface{}, err + } + + i, err := xnet.First(xnet.Filter(interfaces, xnet.IsBroadcast), xnet.HasAddr) + if err != nil { + return net.Interface{}, err + } + return i, nil + } + + return *netInterface, nil + +} + +func Clean() error { + if IsLocal && !NoClean { + log.Debug("cleaning temporary local repository") + err := os.RemoveAll(TmpLocal()) + + if err != nil { + return fmt.Errorf("cleaning temporary local repository: %v", err) + } + } + + return nil +} diff --git a/cmd/clairctl/config/config_test.go b/cmd/clairctl/config/config_test.go new file mode 100644 index 00000000..083b67d1 --- /dev/null +++ b/cmd/clairctl/config/config_test.go @@ -0,0 +1,121 @@ +package config + +import ( + "fmt" + "os" + "testing" + + "github.com/jgsqware/clairctl/test" + "github.com/spf13/viper" + + "gopkg.in/yaml.v2" +) + +const defaultValues = ` +clair: + uri: http://localhost + port: 6060 + healthport: 6061 + report: + path: reports + format: html +auth: + insecureskipverify: true +clairctl: + ip: "" + tempfolder: /tmp/clairctl + port: 0 +` + +const customValues = ` +clair: + uri: http://clair + port: 6061 + healthport: 6062 + report: + path: reports/test + format: json +auth: + insecureskipverify: false +clairctl: + ip: "localhost" + tempfolder: /tmp/clairctl/test + port: 64157 +` + +func TestInitDefault(t *testing.T) { + Init("", "INFO") + + cfg := values() + + var expected config + err := yaml.Unmarshal([]byte(defaultValues), &expected) + if err != nil { + t.Fatal(err) + } + + if cfg != expected { + t.Error("Default values are not correct") + } + viper.Reset() +} + +func TestInitCustomLocal(t *testing.T) { + tmpfile := test.CreateConfigFile(customValues, "clairctl.yml", ".") + defer os.Remove(tmpfile) // clean up + fmt.Println(tmpfile) + Init("", "INFO") + + cfg := values() + + var expected config + err := yaml.Unmarshal([]byte(customValues), &expected) + if err != nil { + t.Fatal(err) + } + + if cfg != expected { + t.Error("values are not correct") + } + viper.Reset() +} + +func TestInitCustomHome(t *testing.T) { + tmpfile := test.CreateConfigFile(customValues, "clairctl.yml", ClairctlHome()) + defer os.Remove(tmpfile) // clean up + fmt.Println(tmpfile) + Init("", "INFO") + + cfg := values() + + var expected config + err := yaml.Unmarshal([]byte(customValues), &expected) + if err != nil { + t.Fatal(err) + } + + if cfg != expected { + t.Error("values are not correct") + } + viper.Reset() +} + +func TestInitCustom(t *testing.T) { + tmpfile := test.CreateConfigFile(customValues, "clairctl.yml", "/tmp") + defer os.Remove(tmpfile) // clean up + fmt.Println(tmpfile) + Init(tmpfile, "INFO") + + cfg := values() + + var expected config + err := yaml.Unmarshal([]byte(customValues), &expected) + if err != nil { + t.Fatal(err) + } + + if cfg != expected { + t.Error("values are not correct") + } + viper.Reset() +} diff --git a/cmd/clairctl/contrib/.hyperclair.yml b/cmd/clairctl/contrib/.hyperclair.yml new file mode 100644 index 00000000..7f7cf532 --- /dev/null +++ b/cmd/clairctl/contrib/.hyperclair.yml @@ -0,0 +1,7 @@ +clair: + port: 6060 + healthPort: 6061 + uri: http://clair + report: + path: ./reports + format: html diff --git a/cmd/clairctl/contrib/README.md b/cmd/clairctl/contrib/README.md new file mode 100644 index 00000000..ee37c119 --- /dev/null +++ b/cmd/clairctl/contrib/README.md @@ -0,0 +1,14 @@ +CONTRIBUTION +----------------- + +# Running full dev environnement + +```bash +# Running Authentication server, Registry, Clair +docker-compose up -d + +# Run Any command ex: +go run main.go help +# Or +go run main.go pull registry:5000/wemanity-belgium/ubuntu-git +``` diff --git a/cmd/clairctl/contrib/auth_server/config/auth_config.yml b/cmd/clairctl/contrib/auth_server/config/auth_config.yml new file mode 100644 index 00000000..f205f685 --- /dev/null +++ b/cmd/clairctl/contrib/auth_server/config/auth_config.yml @@ -0,0 +1,29 @@ +server: + addr: :5001 + certificate: /ssl/server.pem + key: /ssl/server.key +token: + issuer: auth_service + expiration: 900 +users: + "": {} + jgsqware: + password: $2y$05$oGKwJ8QJDLBOoTBmC/EQiefIMV1N9Yt9jpX3SqMoRqZRRql6q7yym +acl: +- match: + account: jgsqware + actions: ['*'] +- match: + account: /.+/ + name: ${account}/* + actions: ['*'] +- match: + type: registry + name: catalog + actions: ['*'] + comment: Anonymous users can get catalog. +- match: + account: "" + name: /.*/ + actions: [pull] + comment: Anonymous users can pull everything. diff --git a/cmd/clairctl/contrib/auth_server/ssl/old/server.key b/cmd/clairctl/contrib/auth_server/ssl/old/server.key new file mode 100644 index 00000000..937d2b07 --- /dev/null +++ b/cmd/clairctl/contrib/auth_server/ssl/old/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQClbJbUWDCY31g5 +hyFlhCpgXOntcqR72vVRew6vw9ooN7LKjC/ev1AbBD3JYca1R9vd/7D6wI5rYNR0 +ZtCMxaQLEgvDf166VdrZewkq8UmbVSFwqtBvcZCG7mTapzgap6jD5H/fFoFoSkFa +V3dGWlEaS5emtk7fLjXTKO0OpByuO2mmRqAoTsnN4O3tYfrW7ReBHueibVIpjZo9 +0qyFK3sZ2ewKG98m8OGh1jzOhxM8PZDKs9KjkZ/SJiGwxIQ/Ny+rXci7q3cshQna +n7ac2Wf0nG21j7eNk0lSKOkT3unb2+1B2+59mEtLauoYs1VcEmM55RnVdawvCmIy +LRowuLlDAgMBAAECggEAP3ELz1gbGyXcwgNPDY3Iarh3hncHGfD5UExvb30fN3lU ++lUVLsoUQKg5wffbqz5p1hPvndsnQ4sZL6MWrEZICW7cUBeTDsdKbUnAVFXBMu9N +KdZ4paTaFsVqrGihHafbE3WYjMgmzQZdVfZhafvNStZezLLyQKmKPvddItZRoYfN +sc+iFpT94hPp9Hjs9ClLQv/w9Xt8lVgD1FUh6yAlLUAn77HzbZuyC2nF4gbD2LiS +4G+xHcH77FyAU5W6BRv1DqNsuu0ksX/93GiYx0EebzT/IXa7xc0mYE0758EXk72y +yoznglkPkSOyyhcuI75FKMyYdQGKpyvw+y4aEv5JwQKBgQDTAaQ827Tpn/aMhP7L +jngFgTdfeq/7Q3eZjGgtr5RFnen6YS6WzWigvh5/70ASDziFd4fyd0P41/MjPkO6 +FTFWisRCpW14+mSTUSDmgTQfsONy1Xr2ib4v4CX2NEy+nUsvpdl72dwZAG/fSu3K +MfkVksd5Z56WJ4wxKrB4riHukQKBgQDIsren8ljtxrLepMHvaNLx5Gl1EtrgX3gy +zTuUM2CSQktwBYNsp68AloOi6/tuH8t1xcBc8ywgCZ2WXzYdRL/koXsd2IpOTsLd +m/zGILgRPVow70yoxKxqxW8YYuQ1gLeAOshj8IHGGfnXTvvpNQNvrnja0NzavjFU +tR3aZQb8kwKBgQCOqNx2vQCKt7kEdmKiE1e4OQ3MAvH6SjoRWWmSAdSYYNSxkITk +NkpX61JJouNJknrfWdpTJymQk8hx+oXlyLBL15Qrjxb9pSTcqQw6a/5msryEhisV +hjlMuxpPZDrC4SvVMidhYgE58h6w9ELi4niKimtM/K6uzFwvXbJkVS7h0QKBgErT +Zum0zzcHZ9TedHfACzWoRTEi8HvK3FOEdPwSE6U0FlATniY6dmKvuzBY7wrly8OD +EO8WspLXQuu3X8OVyD2DfxVnkFkVwE1DRQDRXg7/YsrvzRL3EJlWNs9Ov2q7LK8g +O2oXVyr2sFF33y/ZVgijceeTC2R6mIXOaOzt0acFAoGASB7aF8PTT7YzFCGK/x2O +kg4GLJJSlDyhAZzQqe5LBZB+RhkoHZjdQHcMW84iHp8CsFqb3/D8X+5FsDkwBSMP +bN1fCFE03BsqubtKhI9kMz5hP1OhxlMZdMxRscbdRZqo57f3imtXg6laOktYyPOy +uOzr/Cxm5YUQqyAJ/S4zVuc= +-----END PRIVATE KEY----- diff --git a/cmd/clairctl/contrib/auth_server/ssl/old/server.pem b/cmd/clairctl/contrib/auth_server/ssl/old/server.pem new file mode 100644 index 00000000..73fd3cae --- /dev/null +++ b/cmd/clairctl/contrib/auth_server/ssl/old/server.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDXTCCAkWgAwIBAgIJAOMN706JOuJOMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQwHhcNMTYwMTI4MTcwNjE4WhcNMTcwMTI3MTcwNjE4WjBF +MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 +ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEApWyW1FgwmN9YOYchZYQqYFzp7XKke9r1UXsOr8PaKDeyyowv3r9QGwQ9 +yWHGtUfb3f+w+sCOa2DUdGbQjMWkCxILw39eulXa2XsJKvFJm1UhcKrQb3GQhu5k +2qc4Gqeow+R/3xaBaEpBWld3RlpRGkuXprZO3y410yjtDqQcrjtppkagKE7JzeDt +7WH61u0XgR7nom1SKY2aPdKshSt7GdnsChvfJvDhodY8zocTPD2QyrPSo5Gf0iYh +sMSEPzcvq13Iu6t3LIUJ2p+2nNln9JxttY+3jZNJUijpE97p29vtQdvufZhLS2rq +GLNVXBJjOeUZ1XWsLwpiMi0aMLi5QwIDAQABo1AwTjAdBgNVHQ4EFgQUWCDpNrvl +IPntyV7Y4uyoAq+aPiQwHwYDVR0jBBgwFoAUWCDpNrvlIPntyV7Y4uyoAq+aPiQw +DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAROOTzaxkx9YdvnAEj8Pt +Ej1FgiN1ogxrL4RMwyXs5cAdHi0G71En9onB/8CaWXVrrJbu6DosNn2RQmNapxPG +CkT7QfuYVyZF5jtigsxc+W7aLzASLZHHRA0FcgxUAlEUVaxD3xs6U2jMMntp53ij +kOWmalMi5qOBps8PCD9sd9MDejLFihPAIz15l3TgVkbRvtcUlfmMio5AJYzjbm4/ +0c8brR9tOp3qapeT78AhOmsF7zOVygd/BRIBG+Ynzo2DudBUs/j/4VOt9D9XO4I7 +e3UaqN2OMcL5RYZ5cHemAAy9jjq9/NAYUyLLP0DiCe6OY7SKsDlGfkYVLpZMbUth +9w== +-----END CERTIFICATE----- diff --git a/cmd/clairctl/contrib/auth_server/ssl/server.key b/cmd/clairctl/contrib/auth_server/ssl/server.key new file mode 100644 index 00000000..45340de1 --- /dev/null +++ b/cmd/clairctl/contrib/auth_server/ssl/server.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDP8wLYKQgqxNRw +Liy4ZuYGHvDfK/NT0CIlvMJuobPt6NIGi6NRISLyR7eZvo6oHTN3i0ElpblzL0Hq +JmF87d7o7tqVxO2Fq+faslXrKTgMpDiPj+WxUR3igTy2+DZ4ZP00Y4jwaPYtvwSi +sOP2YeM6r+3sSETj5XonIr+/U3mQYkU9GgFQIbPmJnyPb+WaS0fBZ686zeIKvY3Y ++enSCKOjss1QFyTb0TmmwQUcTBCiEXV10FSgzV383ghj+Fq9+nKo3cwooHNYwU+a +/6gpI1GGWR074J7PGeBId4DNItcpR1x58BGmgaVHabsEW+9RdMNnh17QFk3Wt2eT +S5Knl4UTAgMBAAECggEAcmSoZ+kKiRyGEMAV8csJNszGjL5MuQqB/mh8PQfPR00Q +XHFsgjDMXKN/KKBfMbP+oACG8gLcpbSVeg1rC6J/QXxD2qfeUe5jOTdpdFfUcX/V +bYQnQwfwfK3DjJO2wzwq3irzJe1Xn4q5LhZJETyAF8S4CYcn/oY6UFUZTlLJSNcH +chQOFWvjk13DBjGAmZmjwWKxHoZsKs0ioHtShgONpPM8TZU6SmtJxdFD2pBNp+ba +Lj5IQUYWrfCudBlzqvpXmqBlZe1J1MG+FafvAKx2CFbYkVObjRa/5DtQs99qac8y +rhn8uloK9gljiszwwUVq/ImrUICP+20rHW+kLfHeYQKBgQD9U2XqXz0d2asD72wS ++6yhxY4KZ3TD6W3GgfADC/kTfY+pME7KAXr//7paJJP/GtOxsLGRDHV347c3o3js +OGlFWuUSsuJxGq4SwKuo9eRVbOMEXiVlgCuUL5HAk2co1MbKVhSJ5RGbrp6785JO +JJcuNUTlaUsgQExEsIFJmZpbdQKBgQDSJPwl3uZIg0GC4QbQTAG1ueiQ9oPJ9kyz +cjT31ar9L6VrLwY/MMHYKgBD5JLxkq8qL1h9ii/LJKjX7rX3yttu/qtTMO4Y82CP +XnmR5kbODUUfiirQjTQFS3YP390nAewLwRgYPcvpyNIWA6Im6UdFJECLOTUBeiYg +VumEhSe1ZwKBgAEj6faHHThQLYPkBQGE3n8P65bCZnUnTNYy6Yip+iILU6U4UXJ5 +VTtnxEf5mCzyyvcmy3XSr4itnrqCYt31Vwv338YYxgoqS5RMB7nH+ZIk3lS7s8Fk +NU4CdM6AG1vEsWxhvM/uFwkzXQWNkCAH7CJKHRhHRA5OG8nHXZ2eMmKtAoGBAJ0J +1IA8fVys8bTrkprwYcq6/ifugHfZnmHvM9QNEXWZOIXLo2BvgDyYzo/t7T2nv0zI +Ctnt/V9SqvaKxeNB7g+ZMtC9XQC6R2t8T18PddQfqIs0RmCJVNmsFbMxOOQglJQI +HYhoDc1MLGsVFgT8CS2LNMyV2J2c+YbrTCCjHRR7AoGAICzoSClfvjmRg+4TP9/d +rixJF1UX77TnEhcHaFNBDnmSEX0K4rUr1o6GVZCwI+urL7ZmziDdmTDdbXWjqviJ +73COPw798Ox50VoVWssMGZQkXfbkk2yilLbok08ohlvVhzpiyecgbxAe4C6KRWWg +WEALyN3lILlyj1cYknRJ7gk= +-----END PRIVATE KEY----- diff --git a/cmd/clairctl/contrib/auth_server/ssl/server.pem b/cmd/clairctl/contrib/auth_server/ssl/server.pem new file mode 100644 index 00000000..07464fd0 --- /dev/null +++ b/cmd/clairctl/contrib/auth_server/ssl/server.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDezCCAmOgAwIBAgIJAJXlshcLjIlpMA0GCSqGSIb3DQEBCwUAMFQxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxDTALBgNVBAMMBGF1dGgwHhcNMTYwMTI5MDcyMTE3WhcN +MTcwMTI4MDcyMTE3WjBUMQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0 +ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMQ0wCwYDVQQDDARh +dXRoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz/MC2CkIKsTUcC4s +uGbmBh7w3yvzU9AiJbzCbqGz7ejSBoujUSEi8ke3mb6OqB0zd4tBJaW5cy9B6iZh +fO3e6O7alcTthavn2rJV6yk4DKQ4j4/lsVEd4oE8tvg2eGT9NGOI8Gj2Lb8EorDj +9mHjOq/t7EhE4+V6JyK/v1N5kGJFPRoBUCGz5iZ8j2/lmktHwWevOs3iCr2N2Pnp +0gijo7LNUBck29E5psEFHEwQohF1ddBUoM1d/N4IY/havfpyqN3MKKBzWMFPmv+o +KSNRhlkdO+CezxngSHeAzSLXKUdcefARpoGlR2m7BFvvUXTDZ4de0BZN1rdnk0uS +p5eFEwIDAQABo1AwTjAdBgNVHQ4EFgQUcCD00y15Rdvwe8VnwoZee+J+6ucwHwYD +VR0jBBgwFoAUcCD00y15Rdvwe8VnwoZee+J+6ucwDAYDVR0TBAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEAvmlCA49FGGOZS5CWl/NzH3es3N1Gr8MihdAK0vYLxbOM +8qA2PirEjJ6sWSeB0ZthVpk/dcod68r4dpFh7hpypvaEerFbpr+eWa9nf/KVJ/ft +ClLw+iWZpjEjmtSbSg/XIfraWfvwQp9XNMcmIeHvovHd4HyyU1Ulx6aE31wnZ6SJ +UKhTPgft0DRsmvFMc683jjeUg/Ik/XknnCiSyfVvwv7UEUs7sH85mE0p4giJxhEv +7MdGlQkob+58BpzsErjoj+RpZSljna98NpwBZUfbxkYE2KzU0oqPC0zQ8KawPtw1 +OB9O45KN2mJ9dPIAbezQHolrTQ7V+49/nhTghS/T3Q== +-----END CERTIFICATE----- diff --git a/cmd/clairctl/contrib/config/clair.yml b/cmd/clairctl/contrib/config/clair.yml new file mode 100644 index 00000000..d43e5dab --- /dev/null +++ b/cmd/clairctl/contrib/config/clair.yml @@ -0,0 +1,77 @@ +# Copyright 2015 clair authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# The values specified here are the default values that Clair uses if no configuration file is specified or if the keys are not defined. +clair: + database: + # PostgreSQL Connection string + # http://www.postgresql.org/docs/9.4/static/libpq-connect.html + source: postgresql://postgres:5432?sslmode=disable&user=postgres&password=root + + # Number of elements kept in the cache + # Values unlikely to change (e.g. namespaces) are cached in order to save prevent needless roundtrips to the database. + cacheSize: 16384 + + api: + # API server port + port: 6060 + + # Health server port + # This is an unencrypted endpoint useful for load balancers to check to healthiness of the clair server. + healthport: 6061 + + # Deadline before an API request will respond with a 503 + timeout: 900s + + # 32-bit URL-safe base64 key used to encrypt pagination tokens + # If one is not provided, it will be generated. + # Multiple clair instances in the same cluster need the same value. + paginationKey: + + # Optional PKI configuration + # If you want to easily generate client certificates and CAs, try the following projects: + # https://github.com/coreos/etcd-ca + # https://github.com/cloudflare/cfssl + servername: + cafile: + keyfile: + certfile: + + updater: + # Frequency the database will be updated with vulnerabilities from the default data sources + # The value 0 disables the updater entirely. + interval: 2h + + notifier: + # Number of attempts before the notification is marked as failed to be sent + attempts: 3 + + # Duration before a failed notification is retried + renotifyInterval: 2h + + http: + # Optional endpoint that will receive notifications via POST requests + endpoint: + + # Optional PKI configuration + # If you want to easily generate client certificates and CAs, try the following projects: + # https://github.com/cloudflare/cfssl + # https://github.com/coreos/etcd-ca + servername: + cafile: + keyfile: + certfile: + + # Optional HTTP Proxy: must be a valid URL (including the scheme). + proxy: \ No newline at end of file diff --git a/cmd/clairctl/contrib/docker-compose.yml b/cmd/clairctl/contrib/docker-compose.yml new file mode 100644 index 00000000..b70cacc5 --- /dev/null +++ b/cmd/clairctl/contrib/docker-compose.yml @@ -0,0 +1,52 @@ +version: '2' + +services: + auth: + image: cesanta/docker_auth:stable + ports: + - "5001:5001" + volumes: + - ./auth_server/config:/config:ro + - ./auth_server/ssl:/ssl + command: --v=2 --alsologtostderr /config/auth_config.yml + container_name: "auth" + + registry: + image: registry:2.2.1 + ports: + - 5000:5000 + volumes: + - ./auth_server/ssl:/ssl + - registry-data:/var/lib/registry + container_name: "registry" + environment: + - REGISTRY_STORAGE_FILESYSTEM_ROOTDIRECTORY=/var/lib/registry + # - REGISTRY_AUTH=token + # - REGISTRY_AUTH_TOKEN_REALM=https://auth:5001/auth + # - REGISTRY_AUTH_TOKEN_SERVICE="registry" + # - REGISTRY_AUTH_TOKEN_ISSUER="auth_service" + # - REGISTRY_AUTH_TOKEN_ROOTCERTBUNDLE=/ssl/server.pem + + clair: + image: quay.io/coreos/clair:v1.2.6 + volumes: + - /tmp:/tmp + - ./config:/config + - clair-data:/var/local + ports: + - 6060:6060 + - 6061:6061 + container_name: "clair" + command: --log-level=debug --config=/config/clair.yml + + postgres: + image: postgres + container_name: "postgres" + environment: + - POSTGRES_PASSWORD=root + +volumes: + clair-data: + driver: local + registry-data: + driver: local diff --git a/cmd/clairctl/contrib/ssl_registry/registry.crt b/cmd/clairctl/contrib/ssl_registry/registry.crt new file mode 100644 index 00000000..e2de3d99 --- /dev/null +++ b/cmd/clairctl/contrib/ssl_registry/registry.crt @@ -0,0 +1,34 @@ +-----BEGIN CERTIFICATE----- +MIIF8DCCA9igAwIBAgIJAJ5l76MylCA7MA0GCSqGSIb3DQEBCwUAMFgxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxETAPBgNVBAMTCHJlZ2lzdHJ5MB4XDTE3MDIyMTEwNTYy +NVoXDTE4MDIyMTEwNTYyNVowWDELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt +U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDERMA8GA1UE +AxMIcmVnaXN0cnkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDR+Gjf +9eIbiFrHqBnzBFFYuG5IjlDURDY+mv+fy4NvExZZ3Zds8+KDZm9ByhZwgUJ/2BDL +HzxtgXgqbkWGwYvCdreuFKWU1HG5V/FSxfgZDxXDaxwk8tZNq4bI39YF98F/RjFz ++Xez1di724z89Qbxme92OzEhx3fXeDVc2cqCXYS0L8yUmR7Sy7W8LplU0kNt9SvE +rlrrt+WpuM16B1OkxDKXdlzBkO7CTEPWWBVP7GX7rH6daXYq/I1Bo9g0daqopiNx +h8aLoxLguu7B0Tx75sk2iCAa6s3nufnHf1jtOOeqQtS8Z8S0zu2NpcYixINK7zVF +xIxfAxoISjrqjyZFAQ/L99781FYmChIwmnaBUoDvecQHcF95OwVSwNgetPftmeGP +DGPxnDKpwRGr6D9n9+acoVwH7F0ICDO+baQPE5KG7jSBmtzBip21B7MtKNJkfBtI +wvSQRQzobryMon1PpIqsy7hUwL7F4iN/WpnVvgNtfnKRZegGEaa+FDXqS/Z3gksn +yCAr4uqb44QpsWEeGwfJZsluz4lxTjx9TXbzJR1vwDUdHmiGvu51AyfPn5N3PDgd +N1y6I3miWfljIsPl+3wDmWrcsfxxCY/Q0C4eTFmSRgPrRhaprsEanYuq/1FswbbU +NOMT9PgD5VahXYR4TFO3KxpAU53EUNFq1kGNYwIDAQABo4G8MIG5MB0GA1UdDgQW +BBQhTsuacPZRW1GnXUaiCMhIkVQdijCBiQYDVR0jBIGBMH+AFCFOy5pw9lFbUadd +RqIIyEiRVB2KoVykWjBYMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 +ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMREwDwYDVQQDEwhy +ZWdpc3RyeYIJAJ5l76MylCA7MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQAD +ggIBAJf1YpMmTnoaxiWJblvhwiDF/SJUlj1+2FC3wKWCE+EAB0UxistVaqj5tVox +DyXeI21pwicYCUq2csLxfEbjkDwWJ4XN5Qr7Aijk31R6yv9MybblwcVXUYq1gl+n +LCBdl0HndnFhQxxglJgidNq/K026oXWeJ/l9VJqVSSUyPvNknVpdlqIFHqaIvK3F +q+dlzdUMsgbs6/dBFg9JRU2IIVCsGE5H66bPvLUYOxb7bwuApkG25Lr6Y9ICxSwp +4WAp1gfIxA5DU+PWK69TJ0QYp+3vuxexMvtnNvLzZI7y/ErDT1/jTUqUan+AgMjN +NCw2zZhGqXk80Z5/J05BstDW1XQV/xeMIPbxo4zaUKGdYBVgG+xI46AK4eUIeA6F +GEIwI/nXcRwMk7d7kfV5UZX2w5JysmsI0onkHoHobLnEBaumYyxYPO0ddnbsbqOm +upQnQpYmCGJudoAxbkrCAuoawL1JGar+JBQbd1yDxnWreUz4zT6akVvL3W2lcWA6 +YaBEQ5OenrHxEFmooGmTFOd1pkqmQPtuWlsm0oEQ1xzbUU5je2G5UTXk0LX8Eh55 +mue3NqzCOXqIrVZvAisOMGbQ0az+k75/8F1Votd9bF0sTaOuAqIrN7QVxWtCaVCA +LUBdbwLC6sm3Wejojf0HLG9lTrtuRWcnrITpySA3ztcwtrHK +-----END CERTIFICATE----- diff --git a/cmd/clairctl/contrib/ssl_registry/registry.key b/cmd/clairctl/contrib/ssl_registry/registry.key new file mode 100644 index 00000000..5245eb13 --- /dev/null +++ b/cmd/clairctl/contrib/ssl_registry/registry.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKwIBAAKCAgEA0fho3/XiG4hax6gZ8wRRWLhuSI5Q1EQ2Ppr/n8uDbxMWWd2X +bPPig2ZvQcoWcIFCf9gQyx88bYF4Km5FhsGLwna3rhSllNRxuVfxUsX4GQ8Vw2sc +JPLWTauGyN/WBffBf0Yxc/l3s9XYu9uM/PUG8ZnvdjsxIcd313g1XNnKgl2EtC/M +lJke0su1vC6ZVNJDbfUrxK5a67flqbjNegdTpMQyl3ZcwZDuwkxD1lgVT+xl+6x+ +nWl2KvyNQaPYNHWqqKYjcYfGi6MS4LruwdE8e+bJNoggGurN57n5x39Y7TjnqkLU +vGfEtM7tjaXGIsSDSu81RcSMXwMaCEo66o8mRQEPy/fe/NRWJgoSMJp2gVKA73nE +B3BfeTsFUsDYHrT37Znhjwxj8ZwyqcERq+g/Z/fmnKFcB+xdCAgzvm2kDxOShu40 +gZrcwYqdtQezLSjSZHwbSML0kEUM6G68jKJ9T6SKrMu4VMC+xeIjf1qZ1b4DbX5y +kWXoBhGmvhQ16kv2d4JLJ8ggK+Lqm+OEKbFhHhsHyWbJbs+JcU48fU128yUdb8A1 +HR5ohr7udQMnz5+Tdzw4HTdcuiN5oln5YyLD5ft8A5lq3LH8cQmP0NAuHkxZkkYD +60YWqa7BGp2Lqv9RbMG21DTjE/T4A+VWoV2EeExTtysaQFOdxFDRatZBjWMCAwEA +AQKCAgEAncyH3NDoxfJa7zPplJaJIBkzYLn8CxrcfX51YD1NoNuCb7U2ST6c3E3O +jW34IUMzm+rg7BakjlO/4HuRKu4oP9SCxIRl0I08jqOGDMQVaZfJrlzAARCzeBnR +qQN30HJbbHBvWA6DJJcxVDVzJuRq/IXIzl071nwXF8sSp55SMFliExzdLkxJOvi3 +sx5+Q53l6SxZYW37jK1fH4dwfSYmeWyt7OCaYyquFT3Fub/m/HLYTiVb3qdUlIfL +DSq6oOpRgH+joX39/BFpbZVvPCAoyaEvVRlGr5QJfP5qtsCBL38VtAKX6KQ/0/az +10Ffv99aIKXXroBBUmJ9XP+UeZVtlw8ajQh8YFdArB40k92dn+skSo3lCZLoUpJt +ZPWrwTzWlKgGfocuIcsrgRBfBu2oUJmkbG/FW9CHn+FSM8mMwAddyc48+/HyDTfb +3Yz2719mCt7Q2TxCldTQWMqqxg6wtPEw4Z98rH6t+2dPzNBrdk/lk15Df66nrpNG +Exhpu/AO4HWvr9DCh59jb76/IiZcJjYoi+4lzdh0ACBRj/6THV8+QE13W4jkIDDw +xJFq9hFYqlUn1l4N6Mj8yXhYRVOlLozZe0sEkKD0U0AfZGS3BD9s044Dw4Cre7g+ +2KrCt3zJ79x1L8UjItMZsxUmeJgdbX4J922yvANn51J5hM1PbuECggEBAPiMtHGq +2As3IPECbgoa3/dueAwmoq85AlkyB2h3GDo6seo/pkCC+6p2u/T1XmeLZX9FzQqx +aSPJeO8it/8j1pQEdJAybFDQRQJlou3nMLa3hXll2T7A5/aP0I51emIAWybCaEX1 +zTixeZhMivAjG447IWIk3GzrWpylyndfvVWP2e/8q+bQjaPa1VQWNN/23H1N7KS5 +dRvfIpl72ZFKGnP8SVGVikpGX8AtHk9Ac3qT4xR4r/0i5AribwqAGZy1+m1AgiZW +k103utz8bAlSo8mquUPBvLKOus6+16UbTrVDE/7Cn3vJJaDxWpLQgXmnefpntKD7 +wLKKScbHm0ECWNMCggEBANhDqLXH50n3OYmjnLFa86rARbzW7d0ZO96JEd/beNsd +cb+36dEb/uybj9hdvARScshFjNLcj7tPSSHxlCmbvpTdxYu3pt2QOa4r/pL2RGvM +TVDfXA+f40BIVCiMTN5eN9ErTgGc+06tgpDlTghcoZrmw7rtG9v+GmVe+akBVcqI +mwA9yqibpxKHH5muDzwxr/Hx4dZ27jgHjOJJGjMCMIkcCboZp/CyPxX+gIRTroPs +qog3HuY9paONVo4XXCRfSyfmoJNffOjxflgDr69PUZ+aM+VTpLN7JRUwMuwSWr4y +BSEWn6XBkNzgo779qVlwcpwgXQr1LGtT3QhjjqufHzECggEBAJgJsgNqB1fs9BiZ +bOh/ggsgJwz/wTpAPECFiuSLHWXZK6XoI3GI7htbICR6x7G9ImwVLZTh6ze58WEO +stC+gm7uvsLKJVnV3LDXrS+r4S+T2XDmLVrms74uQNwz3pX+M8Pk3dYVwuBwJ7pS +8BZu01dQsl4PwEpcOYRjIhOdm/qv0RetTxYU8t+NaDtUjimGH2AC/8PPsmRHPSn4 +CaGHW+EhLVRbjkla/Q1YTBccjMcpmZmXLchBxI8n7dbVf1VOOA8Gi9aZ1PELuyGc +wxV82LXu2f8pjp0HFByNvum/Z4kXrC6FrPsSkxL9MHNoWhspqELVlzd2aGyOjQys +YzsEDYMCggEBANGQxuTYQQ2Q74WsMURAAY1+YlW65KbzM+vSYarOj4+tObPxsTc8 +bMy1di/RrUd26dmeY/dVWkbFbvXglpW3YXf6a9qXbbCYePyJj1i0IdtgD7AFsb1G +T73UGRFt23NEU8xyrVWs3G4Cf1qPig1aThO/+P2jlPKaitOetEmMjKkFtUYHmuHG +a6DtpbaTUBohgADxRso/V1qeHmyNMEErpwLGU7qt7+qzn6RdigYw3RTj+uCioWO1 +a1RQuwZYJqbsXPTebM5CotVMZwU9FTrJnywNDqr0Yc62z1l36nCO3LYf3I6S0MOc +Dher66FBR6Du8XDPf7oFmTSsAK2HZBJ00JECggEBAIzPXUN78o3wk+RRw6r/ioy4 +PDDiuD3fXYKBtE+/EbGK850mWqb2/1eJTBTiSTezUgc4clnDLENPXYT/3AjBavVf +PIz9aliUzltV2qiatKWllFJoxJAE9XM81EdJUB/Z531MygjOJ6qRtxq638AeYrWa +j2XSizhH4S0vid7dGlBPNG0uuG07pgMXvkFbkz7BEzP1VGJbjnV+Tbc455zTG8BD +bihiGCfKMDF8ghIriBkUImSLcIG/gFE1J4/jcrZbug7duFIzws2kOZ+D32efcytG +vFnFR+yPS5eGGtgWIc3neOiEalFG1/jhhXAnjxXoStkxXkW6yMVwdIAlOynfFtI= +-----END RSA PRIVATE KEY----- diff --git a/cmd/clairctl/docker/docker.go b/cmd/clairctl/docker/docker.go new file mode 100644 index 00000000..e20a047f --- /dev/null +++ b/cmd/clairctl/docker/docker.go @@ -0,0 +1,54 @@ +package docker + +import ( + "errors" + "reflect" + + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/docker/docker/reference" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker/dockercli" + "github.com/jgsqware/clairctl/docker/dockerdist" + "github.com/opencontainers/go-digest" +) + +//RetrieveManifest get manifest from local or remote docker registry +func RetrieveManifest(imageName string, withExport bool) (image reference.NamedTagged, manifest distribution.Manifest, err error) { + + if !config.IsLocal { + image, manifest, err = dockerdist.DownloadManifest(imageName, true) + } else { + image, manifest, err = dockercli.GetLocalManifest(imageName, withExport) + } + return +} + +//GetLayerDigests return layer digests from manifest schema1 and schema2 +func GetLayerDigests(manifest distribution.Manifest) ([]digest.Digest, error) { + layers := []digest.Digest{} + + switch manifest.(type) { + case schema1.SignedManifest: + for _, l := range manifest.(schema1.SignedManifest).FSLayers { + layers = append(layers, l.BlobSum) + } + case *schema1.SignedManifest: + for _, l := range manifest.(*schema1.SignedManifest).FSLayers { + layers = append(layers, l.BlobSum) + } + case *schema2.DeserializedManifest: + for _, d := range manifest.(*schema2.DeserializedManifest).Layers { + layers = append(layers, d.Digest) + } + case schema2.DeserializedManifest: + for _, d := range manifest.(schema2.DeserializedManifest).Layers { + layers = append(layers, d.Digest) + } + default: + return nil, errors.New("Not supported manifest schema type: " + reflect.TypeOf(manifest).String()) + } + + return layers, nil +} diff --git a/cmd/clairctl/docker/dockercli/dockercli.go b/cmd/clairctl/docker/dockercli/dockercli.go new file mode 100644 index 00000000..4edde6ab --- /dev/null +++ b/cmd/clairctl/docker/dockercli/dockercli.go @@ -0,0 +1,224 @@ +package dockercli + +import ( + "compress/bzip2" + "compress/gzip" + "context" + "encoding/json" + "io" + "io/ioutil" + "os" + "strings" + "syscall" + + "github.com/artyom/untar" + "github.com/coreos/pkg/capnslog" + "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/docker/client" + "github.com/docker/docker/image" + "github.com/docker/docker/layer" + "github.com/docker/docker/reference" + "github.com/jgsqware/clairctl/config" + "github.com/opencontainers/go-digest" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "dockercli") + +//GetLocalManifest retrieve manifest for local image +func GetLocalManifest(imageName string, withExport bool) (reference.NamedTagged, distribution.Manifest, error) { + + n, err := reference.ParseNamed(imageName) + if err != nil { + return nil, nil, err + } + var image reference.NamedTagged + if reference.IsNameOnly(n) { + image = reference.WithDefaultTag(n).(reference.NamedTagged) + } else { + image = n.(reference.NamedTagged) + } + if err != nil { + return nil, nil, err + } + var manifest distribution.Manifest + if withExport { + manifest, err = save(image.Name() + ":" + image.Tag()) + } else { + manifest, err = historyFromCommand(image.Name() + ":" + image.Tag()) + } + + if err != nil { + return nil, schema1.SignedManifest{}, err + } + m := manifest.(schema1.SignedManifest) + m.Name = image.Name() + m.Tag = image.Tag() + return image, m, err +} + +func saveImage(imageName string, fo *os.File) error { + + return nil + // save.Stderr = &stderr + + // save.Stdout = writer + // err := save.Run() + // if err != nil { + // return errors.New(stderr.String()) + // } + + // return nil +} + +func save(imageName string) (distribution.Manifest, error) { + path := config.TmpLocal() + "/" + strings.Split(imageName, ":")[0] + "/blobs" + + if _, err := os.Stat(path); os.IsExist(err) { + err := os.RemoveAll(path) + if err != nil { + return nil, err + } + } + + err := os.MkdirAll(path, 0755) + if err != nil { + return nil, err + } + + log.Debug("docker image to save: ", imageName) + log.Debug("saving in: ", path) + + cli, err := client.NewEnvClient() + if err != nil { + panic(err) + } + + img, err := cli.ImageSave(context.Background(), []string{imageName}) + if err != nil { + panic(err) + } + all, err := ioutil.ReadAll(img) + if err != nil { + panic(err) + } + img.Close() + + fo, err := os.Create(path + "/output.tar") + // close fo on exit and check for its returned error + defer func() { + if err := fo.Close(); err != nil { + panic(err) + } + }() + + if err != nil { + return nil, err + } + + if _, err := fo.Write(all); err != nil { + panic(err) + } + + err = openAndUntar(path+"/output.tar", path) + if err != nil { + return nil, err + } + + err = os.Remove(path + "/output.tar") + if err != nil { + return nil, err + } + return historyFromManifest(path) +} + +func historyFromManifest(path string) (distribution.Manifest, error) { + mf, err := os.Open(path + "/manifest.json") + defer mf.Close() + + if err != nil { + return schema1.SignedManifest{}, err + } + + // https://github.com/docker/docker/blob/master/image/tarexport/tarexport.go#L17 + type manifestItem struct { + Config string + RepoTags []string + Layers []string + Parent image.ID `json:",omitempty"` + LayerSources map[layer.DiffID]distribution.Descriptor `json:",omitempty"` + } + + var manifest []manifestItem + if err = json.NewDecoder(mf).Decode(&manifest); err != nil { + return schema1.SignedManifest{}, err + } else if len(manifest) != 1 { + return schema1.SignedManifest{}, err + } + var layers []string + for _, layer := range manifest[0].Layers { + layers = append(layers, strings.TrimSuffix(layer, "/layer.tar")) + } + var m schema1.SignedManifest + + for _, layer := range manifest[0].Layers { + var d digest.Digest + d, err := digest.Parse("sha256:" + strings.TrimSuffix(layer, "/layer.tar")) + if err != nil { + return schema1.SignedManifest{}, err + } + m.FSLayers = append(m.FSLayers, schema1.FSLayer{BlobSum: d}) + } + + return m, nil +} + +func historyFromCommand(imageName string) (schema1.SignedManifest, error) { + + client, err := client.NewEnvClient() + if err != nil { + return schema1.SignedManifest{}, err + } + histories, err := client.ImageHistory(context.Background(), imageName) + + manifest := schema1.SignedManifest{} + for _, history := range histories { + var d digest.Digest + d, err := digest.Parse(history.ID) + if err != nil { + return schema1.SignedManifest{}, err + } + manifest.FSLayers = append(manifest.FSLayers, schema1.FSLayer{BlobSum: d}) + } + return manifest, nil +} + +func openAndUntar(name, dst string) error { + var rd io.Reader + f, err := os.Open(name) + defer f.Close() + + if err != nil { + return err + } + rd = f + if strings.HasSuffix(name, ".gz") || strings.HasSuffix(name, ".tgz") { + gr, err := gzip.NewReader(f) + if err != nil { + return err + } + defer gr.Close() + rd = gr + } else if strings.HasSuffix(name, ".bz2") { + rd = bzip2.NewReader(f) + } + if err := os.MkdirAll(dst, os.ModeDir|os.ModePerm); err != nil { + return err + } + // resetting umask is essential to have exact permissions on unpacked + // files; it's not not put inside untar function as it changes + // process-wide umask + mask := syscall.Umask(0) + defer syscall.Umask(mask) + return untar.Untar(rd, dst) +} diff --git a/cmd/clairctl/docker/dockerdist/auth.go b/cmd/clairctl/docker/dockerdist/auth.go new file mode 100644 index 00000000..e67138cf --- /dev/null +++ b/cmd/clairctl/docker/dockerdist/auth.go @@ -0,0 +1,83 @@ +package dockerdist + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" + "strings" + + "github.com/jgsqware/clairctl/config" +) + +//ErrUnauthorized is return when requested user don't have access to the resource +var ErrUnauthorized = errors.New("unauthorized access") + +//bearerAuthParams parse Bearer Token on Www-Authenticate header +func bearerAuthParams(r *http.Response) map[string]string { + s := strings.SplitN(r.Header.Get("Www-Authenticate"), " ", 2) + if len(s) != 2 || s[0] != "Bearer" { + return nil + } + result := map[string]string{} + + for _, kv := range strings.Split(s[1], ",") { + parts := strings.Split(kv, "=") + if len(parts) != 2 { + continue + } + result[strings.Trim(parts[0], "\" ")] = strings.Trim(parts[1], "\" ") + } + return result +} + +//AuthenticateResponse add authentication headers on request +func AuthenticateResponse(client *http.Client, dockerResponse *http.Response, request *http.Request) error { + bearerToken := bearerAuthParams(dockerResponse) + url := bearerToken["realm"] + "?service=" + url.QueryEscape(bearerToken["service"]) + if bearerToken["scope"] != "" { + url += "&scope=" + bearerToken["scope"] + } + req, err := http.NewRequest("GET", url, nil) + + if err != nil { + return err + } + + authConfig, err := GetAuthCredentials(config.ImageName) + if err != nil { + return err + } + + req.SetBasicAuth(authConfig.Username, authConfig.Password) + + response, err := client.Do(req) + + if err != nil { + return err + } + defer response.Body.Close() + + if response.StatusCode == http.StatusUnauthorized { + return ErrUnauthorized + } + + if response.StatusCode != http.StatusOK { + return fmt.Errorf("authentication server response: %v - %v", response.StatusCode, response.Status) + } + + type token struct { + Value string `json:"token"` + } + var tok token + err = json.NewDecoder(response.Body).Decode(&tok) + + if err != nil { + return err + } + + request.Header.Set("Authorization", "Bearer "+tok.Value) + + return nil +} diff --git a/cmd/clairctl/docker/dockerdist/dockerdist.go b/cmd/clairctl/docker/dockerdist/dockerdist.go new file mode 100644 index 00000000..673f257b --- /dev/null +++ b/cmd/clairctl/docker/dockerdist/dockerdist.go @@ -0,0 +1,271 @@ +// Copyright 2016 CoreOS, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package dockerdist provides helper methods for retrieving and parsing a +// information from a remote Docker repository. +package dockerdist + +import ( + "errors" + "net/url" + "reflect" + + "strings" + + "github.com/coreos/pkg/capnslog" + distlib "github.com/docker/distribution" + "github.com/docker/distribution/manifest/schema1" + "github.com/docker/distribution/manifest/schema2" + "github.com/docker/distribution/registry/api/v2" + "github.com/docker/distribution/registry/client" + "github.com/docker/docker/api/types" + "github.com/docker/docker/cli/config" + "github.com/docker/docker/distribution" + "github.com/docker/docker/dockerversion" + "github.com/docker/docker/reference" + "github.com/docker/docker/registry" + "github.com/opencontainers/go-digest" + "github.com/spf13/viper" + "golang.org/x/net/context" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "dockerdist") + +var ErrTagNotFound = errors.New("this image or tag is not found") + +func isInsecureRegistry(registryHostname string) bool { + for _, r := range viper.GetStringSlice("docker.insecure-registries") { + if r == registryHostname { + return true + } + } + + return false +} + +func getService() *registry.DefaultService { + serviceOptions := registry.ServiceOptions{ + InsecureRegistries: viper.GetStringSlice("docker.insecure-registries"), + } + return registry.NewService(serviceOptions) +} + +// getRepositoryClient returns a client for performing registry operations against the given named +// image. +func getRepositoryClient(image reference.Named, insecure bool, scopes ...string) (distlib.Repository, error) { + service := getService() + log.Debugf("Retrieving repository client") + + ctx := context.Background() + authConfig, err := GetAuthCredentials(image.String()) + if err != nil { + log.Debugf("GetAuthCredentials error: %v", err) + return nil, err + } + + if (types.AuthConfig{}) != authConfig { + + userAgent := dockerversion.DockerUserAgent(ctx) + _, _, err = service.Auth(ctx, &authConfig, userAgent) + if err != nil { + log.Debugf("Auth: err: %v", err) + return nil, err + } + } + + repoInfo, err := service.ResolveRepository(image) + if err != nil { + log.Debugf("ResolveRepository err: %v", err) + return nil, err + } + + metaHeaders := map[string][]string{} + + endpoints, err := service.LookupPullEndpoints(image.Hostname()) + if err != nil { + log.Debugf("registry.LookupPullEndpoints error: %v", err) + return nil, err + } + + var confirmedV2 bool + var repository distlib.Repository + for _, endpoint := range endpoints { + if confirmedV2 && endpoint.Version == registry.APIVersion1 { + log.Debugf("Skipping v1 endpoint %s because v2 registry was detected", endpoint.URL) + continue + } + + endpoint.TLSConfig.InsecureSkipVerify = viper.GetBool("auth.insecureSkipVerify") + if isInsecureRegistry(endpoint.URL.Host) { + endpoint.URL.Scheme = "http" + } + log.Debugf("endpoint.TLSConfig.InsecureSkipVerify: %v", endpoint.TLSConfig.InsecureSkipVerify) + repository, confirmedV2, err = distribution.NewV2Repository(ctx, repoInfo, endpoint, metaHeaders, &authConfig, scopes...) + if err != nil { + log.Debugf("cannot instanciate new v2 repository on %v", endpoint.URL) + return nil, err + } + + if !confirmedV2 { + return nil, errors.New("Only V2 repository are supported") + } + break + } + + return repository, nil +} + +func GetPushURL(hostname string) (*url.URL, error) { + service := getService() + endpoints, err := service.LookupPushEndpoints(hostname) + if err != nil { + log.Debugf("registry.LookupPushEndpoints error: %v", err) + return nil, err + } + + for _, endpoint := range endpoints { + endpoint.TLSConfig.InsecureSkipVerify = viper.GetBool("auth.insecureSkipVerify") + if isInsecureRegistry(endpoint.URL.Host) { + endpoint.URL.Scheme = "http" + } + return endpoint.URL, nil + } + + return nil, errors.New("No endpoints found") +} + +// getDigest returns the digest for the given image. +func getDigest(ctx context.Context, repo distlib.Repository, image reference.Named) (digest.Digest, error) { + if withDigest, ok := image.(reference.Canonical); ok { + return withDigest.Digest(), nil + } + // Get TagService. + tagSvc := repo.Tags(ctx) + + // Get Tag name. + tag := "latest" + if withTag, ok := image.(reference.NamedTagged); ok { + tag = withTag.Tag() + } + + // Get Tag's Descriptor. + descriptor, err := tagSvc.Get(ctx, tag) + if err != nil { + + // Docker returns an UnexpectedHTTPResponseError if it cannot parse the JSON body of an + // unexpected error. Unfortunately, HEAD requests *by definition* don't have bodies, so + // Docker will return this error for non-200 HEAD requests. We therefore have to hack + // around it... *sigh*. + if _, ok := err.(*client.UnexpectedHTTPResponseError); ok { + return "", errors.New("Received error when trying to fetch the specified tag: it might not exist or you do not have access") + } + + if strings.Contains(err.Error(), v2.ErrorCodeManifestUnknown.Message()) { + return "", ErrTagNotFound + } + + return "", err + } + + return descriptor.Digest, nil +} + +// GetAuthCredentials returns the auth credentials (if any found) for the given repository, as found +// in the user's docker config. +func GetAuthCredentials(image string) (types.AuthConfig, error) { + // Lookup the index information for the name. + indexInfo, err := registry.ParseSearchIndexInfo(image) + if err != nil { + return types.AuthConfig{}, err + } + // Retrieve the user's Docker configuration file (if any). + configFile, err := config.Load(config.Dir()) + if err != nil { + return types.AuthConfig{}, err + } + + // Resolve the authentication information for the registry specified, via the config file. + return registry.ResolveAuthConfig(configFile.AuthConfigs, indexInfo), nil +} + +// DownloadManifest the manifest for the given image, using the given credentials. +func DownloadManifest(image string, insecure bool) (reference.NamedTagged, distlib.Manifest, error) { + log.Debugf("Downloading manifest for %v", image) + // Parse the image name as a docker image reference. + n, err := reference.ParseNamed(image) + if err != nil { + return nil, nil, err + } + if reference.IsNameOnly(n) { + n, _ = reference.ParseNamed(image + ":" + reference.DefaultTag) + } + + named := n.(reference.NamedTagged) + + // Create a reference to a repository client for the repo. + repo, err := getRepositoryClient(named, insecure, "pull") + if err != nil { + return nil, nil, err + } + // Get the digest. + ctx := context.Background() + + digest, err := getDigest(ctx, repo, named) + if err != nil { + return nil, nil, err + } + + // Retrieve the manifest for the tag. + manSvc, err := repo.Manifests(ctx) + if err != nil { + return nil, nil, err + } + manifest, err := manSvc.Get(ctx, digest) + if err != nil { + return nil, nil, err + } + + // Verify the manifest if it's signed. + log.Debugf("manifest type: %v", reflect.TypeOf(manifest)) + + switch manifest.(type) { + case *schema1.SignedManifest: + _, verr := schema1.Verify(manifest.(*schema1.SignedManifest)) + if verr != nil { + return nil, nil, verr + } + case *schema2.DeserializedManifest: + log.Debugf("retrieved schema2 manifest, no verification") + default: + log.Printf("Could not verify manifest for image %v: not signed", image) + } + + return named, manifest, nil +} + +// DownloadV1Manifest the manifest for the given image in v1 schema format, using the given credentials. +func DownloadV1Manifest(imageName string, insecure bool) (reference.NamedTagged, schema1.SignedManifest, error) { + image, manifest, err := DownloadManifest(imageName, insecure) + + if err != nil { + return nil, schema1.SignedManifest{}, err + } + // Ensure that the manifest type is supported. + switch manifest.(type) { + case *schema1.SignedManifest: + return image, *manifest.(*schema1.SignedManifest), nil + default: + return nil, schema1.SignedManifest{}, errors.New("only v1 manifests are currently supported") + } +} diff --git a/cmd/clairctl/main.go b/cmd/clairctl/main.go new file mode 100644 index 00000000..9f0a216a --- /dev/null +++ b/cmd/clairctl/main.go @@ -0,0 +1,21 @@ +// Copyright © 2016 NAME HERE +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "github.com/jgsqware/clairctl/cmd" + +func main() { + cmd.Execute() +} diff --git a/cmd/clairctl/server/server.go b/cmd/clairctl/server/server.go new file mode 100644 index 00000000..c4346f17 --- /dev/null +++ b/cmd/clairctl/server/server.go @@ -0,0 +1,120 @@ +package server + +import ( + "crypto/tls" + "net" + "net/http" + "net/http/httputil" + "net/url" + "os" + "regexp" + "strings" + "time" + + "github.com/coreos/pkg/capnslog" + "github.com/jgsqware/clairctl/clair" + "github.com/jgsqware/clairctl/config" + "github.com/jgsqware/clairctl/docker/dockerdist" + "github.com/spf13/viper" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "server") + +//Serve run a local server with the fileserver and the reverse proxy +func Serve(sURL string) error { + + go func() { + http.Handle("/v2/", newSingleHostReverseProxy()) + http.Handle("/local/", http.StripPrefix("/local", restrictedFileServer(config.TmpLocal()))) + + listener := tcpListener(sURL) + log.Info("Starting Server on ", listener.Addr()) + + if err := http.Serve(listener, nil); err != nil { + log.Fatalf("local server error: %v", err) + } + }() + //sleep needed to wait the server start. Maybe use a channel for that + time.Sleep(5 * time.Millisecond) + return nil +} + +func tcpListener(sURL string) (listener net.Listener) { + listener, err := net.Listen("tcp", sURL) + if err != nil { + log.Fatalf("cannot instanciate listener: %v", err) + } + + if viper.GetInt("clairctl.port") == 0 { + port := strings.Split(listener.Addr().String(), ":")[1] + log.Debugf("Update local server port from %q to %q", "0", port) + viper.Set("clairctl.port", port) + } + + return +} + +func restrictedFileServer(path string) http.Handler { + if _, err := os.Stat(path); os.IsNotExist(err) { + os.Mkdir(path, 0777) + } + + fc := func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(path)).ServeHTTP(w, r) + } + return http.HandlerFunc(fc) +} + +func newSingleHostReverseProxy() *httputil.ReverseProxy { + director := func(request *http.Request) { + + var validID = regexp.MustCompile(`.*/blobs/(.*)$`) + u := request.URL.Path + log.Debugf("request url: %v", u) + log.Debugf("request for image: %v", config.ImageName) + if !validID.MatchString(u) { + log.Errorf("cannot parse url: %v", u) + } + var host string + host, err := clair.GetRegistryMapping(validID.FindStringSubmatch(u)[1]) + log.Debugf("host retreived: %v", host) + if err != nil { + log.Errorf("response error: %v", err) + return + } + out, _ := url.Parse(host) + request.URL.Scheme = out.Scheme + request.URL.Host = out.Host + client := &http.Client{Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: viper.GetBool("auth.insecureSkipVerify")}, + DisableCompression: true, + }} + + log.Debugf("auth.insecureSkipVerify: %v", viper.GetBool("auth.insecureSkipVerify")) + log.Debugf("request.URL.String(): %v", request.URL.String()) + req, _ := http.NewRequest("HEAD", request.URL.String(), nil) + + resp, err := client.Do(req) + if err != nil { + log.Errorf("response error: %v", err) + return + } + + if resp.StatusCode == http.StatusUnauthorized { + log.Info("pull from clair is unauthorized") + dockerdist.AuthenticateResponse(client, resp, request) + } + + r, _ := http.NewRequest("GET", request.URL.String(), nil) + r.Header.Set("Authorization", request.Header.Get("Authorization")) + r.Header.Set("Accept-Encoding", request.Header.Get("Accept-Encoding")) + *request = *r + } + return &httputil.ReverseProxy{ + Director: director, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: viper.GetBool("auth.insecureSkipVerify")}, + DisableCompression: true, + }, + } +} diff --git a/cmd/clairctl/test/test.go b/cmd/clairctl/test/test.go new file mode 100644 index 00000000..08826351 --- /dev/null +++ b/cmd/clairctl/test/test.go @@ -0,0 +1,39 @@ +package test + +import ( + "io/ioutil" + "os" + + "github.com/coreos/pkg/capnslog" +) + +var log = capnslog.NewPackageLogger("github.com/jgsqware/clairctl", "test") + +func CreateTmpConfigFile(content string) string { + + c := []byte(content) + tmpfile, err := ioutil.TempFile("", "test-clairctl") + if err != nil { + log.Fatal(err) + } + if content != "" { + if _, err := tmpfile.Write(c); err != nil { + log.Fatal(err) + } + if err := tmpfile.Close(); err != nil { + log.Fatal(err) + } + } else { + if err := os.Remove(tmpfile.Name()); err != nil { + log.Fatal(err) + } + } + return tmpfile.Name() +} + +func CreateConfigFile(content string, name string, path string) string { + if err := ioutil.WriteFile(path+"/"+name, []byte(content), 0600); err != nil { + log.Fatal(err) + } + return path + "/" + name +} diff --git a/cmd/clairctl/xstrings/xstrings.go b/cmd/clairctl/xstrings/xstrings.go new file mode 100644 index 00000000..49aa29dc --- /dev/null +++ b/cmd/clairctl/xstrings/xstrings.go @@ -0,0 +1,29 @@ +package xstrings + +import ( + "encoding/json" + "strings" +) + +//Substr extract string of length in s starting at pos +func Substr(s string, pos, length int) string { + runes := []rune(s) + l := pos + length + if l > len(runes) { + l = len(runes) + } + return string(runes[pos:l]) +} + +//TrimPrefixSuffix combine TrimPrefix and TrimSuffix +func TrimPrefixSuffix(s string, prefix string, suffix string) string { + return strings.TrimSuffix(strings.TrimPrefix(s, prefix), suffix) +} + +func ToIndentJSON(v interface{}) ([]byte, error) { + b, err := json.MarshalIndent(v, "", "\t") + if err != nil { + return nil, err + } + return b, nil +} diff --git a/cmd/clairctl/xstrings/xstrings_test.go b/cmd/clairctl/xstrings/xstrings_test.go new file mode 100644 index 00000000..d58dd13c --- /dev/null +++ b/cmd/clairctl/xstrings/xstrings_test.go @@ -0,0 +1,27 @@ +package xstrings + +import "testing" + +func TestSubstrFromBeginning(t *testing.T) { + commitID := "e3ff9321271b0a5cec45ca6e0cdc72b2f376afd2" + expected := "e3ff9" + if s := Substr(commitID, 0, 5); s != expected { + t.Errorf("is %v, expect %v", s, expected) + } +} + +func TestSubstrFromCharFive(t *testing.T) { + commitID := "e3ff9321271b0a5cec45ca6e0cdc72b2f376afd2" + expected := "32127" + if s := Substr(commitID, 5, 5); s != expected { + t.Errorf("is %v, expect %v", s, expected) + } +} + +func TestTrimPrefixSuffix(t *testing.T) { + v := "http://registry:5555/v2" + e := "registry:5555" + if s := TrimPrefixSuffix(v, "http://", "/v2"); s != e { + t.Errorf("is %v, expect %v", s, e) + } +} diff --git a/glide.yaml b/glide.yaml index 80a1800f..1bec37f6 100644 --- a/glide.yaml +++ b/glide.yaml @@ -91,3 +91,32 @@ import: version: ^0.1.0 - package: github.com/kr/text - package: github.com/remind101/migrate + +- package: github.com/artyom/untar +- package: github.com/docker/distribution + version: 129ad8ea0c3760d878b34cffdb9c3be874a7b2f7 + subpackages: + - manifest/schema1 + - registry/client +- package: github.com/docker/go-connections + version: 4ccf312bf1d35e5dbda654e57a9be4c3f3cd0366 + subpackages: + - tlsconfig +- package: github.com/spf13/cobra +- package: github.com/spf13/viper +- package: github.com/docker/go-units + version: ^0.3.1 +- package: github.com/docker/docker + version: 48dd90d3985889ca008faa3b041bf31d2ada95c5 + subpackages: + - api/types + - cli/config + - distribution + - reference + - registry +- package: github.com/spf13/pflag + version: 7597b2702729ebb651fc9bb2adac40bcc62db82d +- package: golang.org/x/crypto + version: ca7e7f10cb9fd9c1a6ff7f60436c086d73714180 +- package: github.com/opencontainers/go-digest + version: 21dfd564fd89c944783d00d069f33e3e7123c448 \ No newline at end of file