diff --git a/Documentation/concepts/notifications.md b/Documentation/concepts/notifications.md index b9eefbaa2a..f650789434 100644 --- a/Documentation/concepts/notifications.md +++ b/Documentation/concepts/notifications.md @@ -53,7 +53,7 @@ type VulnSummary struct { When you configure notifier for webhook delivery you provide the service with the following pieces of information: * A target URL where the webhook will fire * The callback URL where the notifier may be reached including its API path - * e.g. "http://clair-notifier/api/v1/notifications" + * e.g. "http://clair-notifier/notifier/api/v1/notifications" When the notifier has determined an updated security database has changed the affected status of an indexed manifest, it will deliver the following JSON body to the configured target: ```json @@ -72,7 +72,7 @@ The URL returned in the callback field brings the client to a paginated result. The callback endpoint specification follows: ```go -GET /api/v1/notification/{id}?[page_size=N][next=N] +GET /notifier/api/v1/notification/{id}?[page_size=N][next=N] { page: { size: int, // maximum number of notifications in the response @@ -99,10 +99,10 @@ When the final page is served to the client the returned "page" data structure w Therefore the following loop is valid for obtaining all notifications for a notification id in pages of a specified size. ``` -{ page, notifications } = http.Get("http://clairv4/api/v1/notifications/{id}?page_size=1000") +{ page, notifications } = http.Get("http://clairv4/notifier/api/v1/notifications/{id}?page_size=1000") while (page.Next != None) { - { page, notifications } = http.Get("http://clairv4/api/v1/notifications/{id}?next={page.Next},page_size=1000") + { page, notifications } = http.Get("http://clairv4/notifier/api/v1/notifications/{id}?next={page.Next},page_size=1000") } ``` diff --git a/Documentation/howto/deployment.md b/Documentation/howto/deployment.md index efb48dbfca..7d5b01c5ac 100644 --- a/Documentation/howto/deployment.md +++ b/Documentation/howto/deployment.md @@ -99,13 +99,13 @@ When the load balancer encounters a particular path prefix it must send those re For example, this is how we configure Traefik in our local development environment: ``` -"traefik.enable=true" -"traefik.http.routers.notifications.entrypoints=clair" -"traefik.http.routers.notifications.rule=PathPrefix(`/api/v1/notification`)" -"traefik.http.routers.notifications.service=notifications" -"traefik.http.services.notifications.loadbalancer.server.port=6000" +- "traefik.enable=true" +- "traefik.http.routers.notifier.entrypoints=clair" +- "traefik.http.routers.notifier.rule=PathPrefix(`/notifier`)" +- "traefik.http.routers.notifier.service=notifier" +- "traefik.http.services.notifier.loadbalancer.server.port=6000" ``` -This configuration is saying "take any paths prefixes of /api/v1/notification and send them to the notifier services on port 6060" +This configuration is saying "take any paths prefixes of /notifier/ and send them to the notifier services on port 6000" Every load balancer will have their own way to perform path routing. Check the documentation for your infrastructure of choice. diff --git a/Documentation/howto/testing.md b/Documentation/howto/testing.md index f0d2e16a57..5e687113e4 100644 --- a/Documentation/howto/testing.md +++ b/Documentation/howto/testing.md @@ -98,3 +98,5 @@ will rip the entire environment down. The most common issue encountered when standing up the dev environment is port conflicts. Make sure that you do not have any other processes listening on any of the ports outlined above. The second issue you may face is your Docker resource settings maybe too constrained to support the local dev stack. This is typically seen on Docker4Mac since a VM is used with a specific set of resources configured. See [Docker For Mac Manual](https://docs.docker.com/docker-for-mac/) for instructions on how to change these resources. + +Lastly, you can view traefik's ui at `localhost:7000`. If traefik is reporting no routers or services its likely SELinux has blocked its access to `/var/run/docker.socket`. Place SELinuse in permissive mode and restart the local development environment. diff --git a/Documentation/reference/api.md b/Documentation/reference/api.md index c52ae6fe23..86e731ca0e 100644 --- a/Documentation/reference/api.md +++ b/Documentation/reference/api.md @@ -42,7 +42,7 @@ headers = { 'Accept': 'application/json' } -r = requests.delete('/api/v1/notification/{notification_id}', headers = headers) +r = requests.delete('/notifier/api/v1/notification/{notification_id}', headers = headers) print(r.json()) @@ -63,7 +63,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("DELETE", "/api/v1/notification/{notification_id}", data) + req, err := http.NewRequest("DELETE", "/notifier/api/v1/notification/{notification_id}", data) req.Header = headers client := &http.Client{} @@ -79,7 +79,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/notification/{notification_id}', +fetch('/notifier/api/v1/notification/{notification_id}', { method: 'DELETE', @@ -93,7 +93,7 @@ fetch('/api/v1/notification/{notification_id}', ``` -`DELETE /api/v1/notification/{notification_id}` +`DELETE notifier/api/v1/notification/{notification_id}` Issues a delete of the provided notification id and all associated notifications. After this delete clients will no longer be able to retrieve notifications. @@ -139,7 +139,7 @@ headers = { 'Accept': 'application/json' } -r = requests.get('/api/v1/notification/{notification_id}', headers = headers) +r = requests.get('/notifier/api/v1/notification/{notification_id}', headers = headers) print(r.json()) @@ -160,7 +160,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/v1/notification/{notification_id}", data) + req, err := http.NewRequest("GET", "/notifier/api/v1/notification/{notification_id}", data) req.Header = headers client := &http.Client{} @@ -176,7 +176,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/notification/{notification_id}', +fetch('/notifier/api/v1/notification/{notification_id}', { method: 'GET', @@ -190,7 +190,7 @@ fetch('/api/v1/notification/{notification_id}', ``` -`GET /api/v1/notification/{notification_id}` +`GET notifier/api/v1/notification/{notification_id}` By performing a GET with a notification_id as a path parameter the client will retrieve a paginated response of notifcation objects @@ -299,7 +299,7 @@ headers = { 'Accept': 'application/json' } -r = requests.post('/api/v1/index_report', headers = headers) +r = requests.post('/indexer/api/v1/index_report', headers = headers) print(r.json()) @@ -321,7 +321,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("POST", "/api/v1/index_report", data) + req, err := http.NewRequest("POST", "/indexer/api/v1/index_report", data) req.Header = headers client := &http.Client{} @@ -354,7 +354,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/index_report', +fetch('/indexer/api/v1/index_report', { method: 'POST', body: inputBody, @@ -368,7 +368,7 @@ fetch('/api/v1/index_report', ``` -`POST /api/v1/index_report` +`POST indexer/api/v1/index_report` By submitting a Manifest object to this endpoint Clair will fetch the layers, scan each layer's contents, and provide an index of discovered @@ -481,7 +481,7 @@ headers = { 'Accept': 'application/json' } -r = requests.get('/api/v1/index_report/{manifest_hash}', headers = headers) +r = requests.get('/indexer/api/v1/index_report/{manifest_hash}', headers = headers) print(r.json()) @@ -502,7 +502,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/v1/index_report/{manifest_hash}", data) + req, err := http.NewRequest("GET", "/indexer/api/v1/index_report/{manifest_hash}", data) req.Header = headers client := &http.Client{} @@ -518,7 +518,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/index_report/{manifest_hash}', +fetch('/indexer/api/v1/index_report/{manifest_hash}', { method: 'GET', @@ -532,7 +532,7 @@ fetch('/api/v1/index_report/{manifest_hash}', ``` -`GET /api/v1/index_report/{manifest_hash}` +`GET indexer/api/v1/index_report/{manifest_hash}` Given a Manifest's content addressable hash an IndexReport will be retrieved if exists. @@ -628,7 +628,7 @@ headers = { 'Accept': 'application/json' } -r = requests.get('/api/v1/index_state', headers = headers) +r = requests.get('/indexer/api/v1/index_state', headers = headers) print(r.json()) @@ -649,7 +649,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/v1/index_state", data) + req, err := http.NewRequest("GET", "/indexer/api/v1/index_state", data) req.Header = headers client := &http.Client{} @@ -665,7 +665,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/index_state', +fetch('/indexer/api/v1/index_state', { method: 'GET', @@ -679,7 +679,7 @@ fetch('/api/v1/index_state', ``` -`GET /api/v1/index_state` +`GET indexer/api/v1/index_state` The index state endpoint returns a json structure indicating the indexer's internal configuration state. @@ -729,7 +729,7 @@ headers = { 'Accept': 'application/json' } -r = requests.get('/api/v1/vulnerability_report/{manifest_hash}', headers = headers) +r = requests.get('/matcher/api/v1/vulnerability_report/{manifest_hash}', headers = headers) print(r.json()) @@ -750,7 +750,7 @@ func main() { } data := bytes.NewBuffer([]byte{jsonReq}) - req, err := http.NewRequest("GET", "/api/v1/vulnerability_report/{manifest_hash}", data) + req, err := http.NewRequest("GET", "/matcher/api/v1/vulnerability_report/{manifest_hash}", data) req.Header = headers client := &http.Client{} @@ -766,7 +766,7 @@ const headers = { 'Accept':'application/json' }; -fetch('/api/v1/vulnerability_report/{manifest_hash}', +fetch('/matcher/api/v1/vulnerability_report/{manifest_hash}', { method: 'GET', @@ -780,7 +780,7 @@ fetch('/api/v1/vulnerability_report/{manifest_hash}', ``` -`GET /api/v1/vulnerability_report/{manifest_hash}` +`GET matcher/api/v1/vulnerability_report/{manifest_hash}` Given a Manifest's content addressable hash a VulnerabilityReport will be created. The Manifest **must** have been Indexed first @@ -1021,7 +1021,7 @@ PagedNotifications ```json { "notification_id": "269886f3-0146-4f08-9bf7-cb1138d48643", - "callback": "http://clair-notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643" + "callback": "http://clair-notifier/notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643" } ``` diff --git a/config.yaml.sample b/config.yaml.sample index 3390937e77..e1c24dd5e5 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -48,7 +48,7 @@ notifier: # webhook, amqp, stomp webhook: target: "http://webhook/" - callback: "http://clair-notifier/api/v1/notifications" + callback: "http://clair-notifier/notifier/api/v1/notifications" amqp: exchange: name: "" @@ -58,7 +58,7 @@ notifier: uris: ["amqp://user:pass@host:10000/vhost"] direct: false routing_key: "notifications" - callback: "http://clair-notifier/api/v1/notifications" + callback: "http://clair-notifier/notifier/api/v1/notifications" tls: root_ca: "optional/path/to/rootca" cert: "madatory/path/to/cert" @@ -66,7 +66,7 @@ notifier: stomp: desitnation: "notifications" direct: false - callback: "http://clair-notifier/api/v1/notifications" + callback: "http://clair-notifier/notifier/api/v1/notifications" login: login: "username" passcode: "passcode" diff --git a/contrib/openshift/manifests/manifests.yaml b/contrib/openshift/manifests/manifests.yaml index 79accb4356..f516a337cc 100644 --- a/contrib/openshift/manifests/manifests.yaml +++ b/contrib/openshift/manifests/manifests.yaml @@ -189,19 +189,19 @@ objects: service: matcher app: clair # - # index_report route + # indexer # - kind: Route apiVersion: route.openshift.io/v1 metadata: - name: clair-index-report + name: clair-indexer namespace: clair labels: app: clair component: indexer spec: host: clair.stage.quay.io - path: /api/v1/index_report + path: /indexer to: kind: Service name: clair-indexer @@ -210,43 +210,43 @@ objects: targetPort: ${{INTROSPECTION_PORT}} wildcardPolicy: None # - # index_state route + # matcher route # - kind: Route apiVersion: route.openshift.io/v1 metadata: - name: clair-index-state + name: clair-matcher namespace: clair labels: app: clair - component: indexer + component: matcher spec: host: clair.stage.quay.io - path: /api/v1/index_state + path: /matcher to: kind: Service - name: clair-indexer + name: clair-matcher weight: 100 port: targetPort: ${{INTROSPECTION_PORT}} wildcardPolicy: None # - # vulnerability_report route + # notifier route # - kind: Route apiVersion: route.openshift.io/v1 metadata: - name: clair-vulnerability-report + name: notifier namespace: clair labels: app: clair - component: matcher + component: notifier spec: host: clair.stage.quay.io - path: /api/v1/vulnerability_report + path: /notifier to: kind: Service - name: clair-matcher + name: clair-notifier weight: 100 port: targetPort: ${{INTROSPECTION_PORT}} diff --git a/docker-compose.yaml b/docker-compose.yaml index 882cf1b1ca..99188edc1c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -70,10 +70,10 @@ services: labels: - "traefik.enable=true" - - "traefik.http.routers.notifications.entrypoints=clair" - - "traefik.http.routers.notifications.rule=PathPrefix(`/api/v1/notification`)" - - "traefik.http.routers.notifications.service=notifications" - - "traefik.http.services.notifications.loadbalancer.server.port=6000" + - "traefik.http.routers.notifier.entrypoints=clair" + - "traefik.http.routers.notifier.rule=PathPrefix(`/notifier`)" + - "traefik.http.routers.notifier.service=notifier" + - "traefik.http.services.notifier.loadbalancer.server.port=6000" # this should only be created and deleted via the make target "local-dev-notifier-test" notifier-test-mode: @@ -90,10 +90,10 @@ services: labels: - "traefik.enable=true" - - "traefik.http.routers.notifications.entrypoints=clair" - - "traefik.http.routers.notifications.rule=PathPrefix(`/api/v1/notification`)" - - "traefik.http.routers.notifications.service=notifications" - - "traefik.http.services.notifications.loadbalancer.server.port=6000" + - "traefik.http.routers.notifier.entrypoints=clair" + - "traefik.http.routers.notifier.rule=PathPrefix(`/notifier`)" + - "traefik.http.routers.notifier.service=notifier" + - "traefik.http.services.notifier.loadbalancer.server.port=6000" indexer: container_name: clair-indexer @@ -108,20 +108,10 @@ services: labels: - "traefik.enable=true" - - "traefik.http.routers.index_report.rule=PathPrefix(`/api/v1/index_report`)" - - "traefik.http.routers.index_report.entrypoints=clair" - - "traefik.http.routers.index_report.service=index_report" - - "traefik.http.services.index_report.loadbalancer.server.port=6000" - - - "traefik.http.services.index_state.loadbalancer.server.port=6000" - - "traefik.http.routers.index_state.entrypoints=clair" - - "traefik.http.routers.index_state.service=index_state" - - "traefik.http.routers.index_state.rule=PathPrefix(`/api/v1/index_state`)" - - - "traefik.http.services.affected_manifest.loadbalancer.server.port=6000" - - "traefik.http.routers.affected_manifest.entrypoints=clair" - - "traefik.http.routers.affected_manifest.service=affected_manifest" - - "traefik.http.routers.affected_manifest.rule=PathPrefix(`/api/v1/internal/affected_manifest`)" + - "traefik.http.routers.indexer.rule=PathPrefix(`/indexer`)" + - "traefik.http.routers.indexer.entrypoints=clair" + - "traefik.http.routers.indexer.service=indexer" + - "traefik.http.services.indexer.loadbalancer.server.port=6000" ## like the indexer service above, but mounts quay's http port into local ## network namespace. @@ -141,20 +131,10 @@ services: labels: - "traefik.enable=true" - - "traefik.http.routers.index_report.rule=PathPrefix(`/api/v1/index_report`)" - - "traefik.http.routers.index_report.entrypoints=clair" - - "traefik.http.routers.index_report.service=index_report" - - "traefik.http.services.index_report.loadbalancer.server.port=6000" - - - "traefik.http.services.index_state.loadbalancer.server.port=6000" - - "traefik.http.routers.index_state.entrypoints=clair" - - "traefik.http.routers.index_state.service=index_state" - - "traefik.http.routers.index_state.rule=PathPrefix(`/api/v1/index_state`)" - - - "traefik.http.services.affected_manifest.loadbalancer.server.port=6000" - - "traefik.http.routers.affected_manifest.entrypoints=clair" - - "traefik.http.routers.affected_manifest.service=affected_manifest" - - "traefik.http.routers.affected_manifest.rule=PathPrefix(`/api/v1/internal/affected_manifest`)" + - "traefik.http.routers.indexer.rule=PathPrefix(`/indexer`)" + - "traefik.http.routers.indexer.entrypoints=clair" + - "traefik.http.routers.indexer.service=indexer" + - "traefik.http.services.indexer.loadbalancer.server.port=6000" depends_on: - quay @@ -171,20 +151,10 @@ services: labels: - "traefik.enable=true" - - "traefik.http.routers.vulnerability_report.rule=PathPrefix(`/api/v1/vulnerability_report`)" - - "traefik.http.routers.vulnerability_report.entrypoints=clair" - - "traefik.http.routers.vulnerability_report.service=vulnerability_report" - - "traefik.http.services.vulnerability_report.loadbalancer.server.port=6000" - - - "traefik.http.routers.update_operation.entrypoints=clair" - - "traefik.http.routers.update_operation.rule=PathPrefix(`/api/v1/internal/update_operation`)" - - "traefik.http.routers.update_operation.service=update_operation" - - "traefik.http.services.update_operation.loadbalancer.server.port=6000" - - - "traefik.http.routers.update_diff.entrypoints=clair" - - "traefik.http.routers.update_diff.rule=PathPrefix(`/api/v1/internal/update_diff`)" - - "traefik.http.routers.update_diff.service=update_diff" - - "traefik.http.services.update_diff.loadbalancer.server.port=6000" + - "traefik.http.routers.matcher.rule=PathPrefix(`/matcher`)" + - "traefik.http.routers.matcher.entrypoints=clair" + - "traefik.http.routers.matcher.service=matcher" + - "traefik.http.services.matcher.loadbalancer.server.port=6000" swagger-ui: container_name: clair-swagger diff --git a/httptransport/discoveryhandler_gen.go b/httptransport/discoveryhandler_gen.go index acb2ef9185..e070b47b13 100644 --- a/httptransport/discoveryhandler_gen.go +++ b/httptransport/discoveryhandler_gen.go @@ -3,6 +3,6 @@ package httptransport const ( - _openapiJSON = `{"components":{"examples":{"Distribution":{"value":{"arch":"","cpe":"","did":"ubuntu","id":"1","name":"Ubuntu","pretty_name":"Ubuntu 18.04.3 LTS","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"}},"Environment":{"value":{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}},"Package":{"value":{"arch":"x86","cpe":"","id":"10","kind":"binary","module":"","name":"libapt-pkg5.0","normalized_version":"","source":{"id":"9","kind":"source","name":"apt","source":null,"version":"1.6.11"},"version":"1.6.11"}},"VulnSummary":{"value":{"description":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","dist":{"arch":"","cpe":"","did":"ubuntu","id":"0","name":"Ubuntu","pretty_name":"","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"},"fixed_in_version":"v0.0.1","links":"http://link-to-advisory","name":"CVE-2009-5155","normalized_severity":"Low","package":{"id":"0","kind":"","name":"glibc","package_db":"","repository_hint":"","source":null,"version":""},"repo":{"id":"0","key":"","name":"Ubuntu 18.04.3 LTS","uri":""}}},"Vulnerability":{"value":{"description":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","dist":{"arch":"","cpe":"","did":"ubuntu","id":"0","name":"Ubuntu","pretty_name":"","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"},"fixed_in_version":"2.28-0ubuntu1","id":"356835","issued":"2019-10-12T07:20:50.52Z","links":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-5155\nhttp://people.canonical.com/~ubuntu-security/cve/2009/CVE-2009-5155.html\nhttps://sourceware.org/bugzilla/show_bug.cgi?id=11053\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=22793\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=32806\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=34238\nhttps://sourceware.org/bugzilla/show_bug.cgi?id=18986\"\n","name":"CVE-2009-5155","normalized_severity":"Low","package":{"id":"0","kind":"","name":"glibc","package_db":"","repository_hint":"","source":null,"version":""},"repo":{"id":"0","key":"","name":"Ubuntu 18.04.3 LTS","uri":""},"severity":"Low","updater":""}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad Request"},"InternalServerError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Internal Server Error"},"MethodNotAllowed":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Method Not Allowed"},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not Found"}},"schemas":{"Callback":{"description":"A callback for clients to retrieve notifications","properties":{"callback":{"description":"the url where notifications can be retrieved","example":"http://clair-notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643","type":"string"},"notification_id":{"description":"the unique identifier for this set of notifications","example":"269886f3-0146-4f08-9bf7-cb1138d48643","type":"string"}},"title":"Callback","type":"object"},"Digest":{"description":"A digest string with prefixed algorithm. The format is described here:\nhttps://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests\n\nDigests are used throughout the API to identify Layers and Manifests.\n","example":"sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3","title":"Digest","type":"string"},"Distribution":{"description":"An indexed distribution discovered in a layer. See\nhttps://www.freedesktop.org/software/systemd/man/os-release.html\nfor explanations and example of fields.\n","example":{"$ref":"#/components/examples/Distribution/value"},"properties":{"arch":{"type":"string"},"cpe":{"type":"string"},"did":{"type":"string"},"id":{"description":"A unique ID representing this distribution","type":"string"},"name":{"type":"string"},"pretty_name":{"type":"string"},"version":{"type":"string"},"version_code_name":{"type":"string"},"version_id":{"type":"string"}},"required":["id","did","name","version","version_code_name","version_id","arch","cpe","pretty_name"],"title":"Distribution","type":"object"},"Environment":{"description":"The environment a particular package was discovered in.","properties":{"distribution_id":{"description":"The distribution ID found in an associated IndexReport or\nVulnerabilityReport.\n","example":"1","type":"string"},"introduced_in":{"$ref":"#/components/schemas/Digest"},"package_db":{"description":"The filesystem path or unique identifier of a package database.\n","example":"var/lib/dpkg/status","type":"string"}},"required":["package_db","introduced_in","distribution_id"],"title":"Environment","type":"object"},"Error":{"description":"A general error schema returned when status is not 200 OK","properties":{"code":{"description":"a code for this particular error","type":"string"},"message":{"description":"a message with further detail","type":"string"}},"title":"Error","type":"object"},"IndexReport":{"description":"A report of the Index process for a particular manifest. A\nclient's usage of this is largely information. Clair uses this\nreport for matching Vulnerabilities.\n","properties":{"distributions":{"additionalProperties":{"$ref":"#/components/schemas/Distribution"},"description":"A map of Distribution objects keyed by their Distribution.id\ndiscovered in the manifest.\n","example":{"1":{"$ref":"#/components/examples/Distribution/value"}},"type":"object"},"environments":{"additionalProperties":{"items":{"$ref":"#/components/schemas/Environment"},"type":"array"},"description":"A map of lists containing Environment objects keyed by the\nassociated Package.id.\n","example":{"10":[{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}]},"type":"object"},"err":{"description":"An error message on event of unsuccessful index","example":"","type":"string"},"manifest_hash":{"$ref":"#/components/schemas/Digest"},"packages":{"additionalProperties":{"$ref":"#/components/schemas/Package"},"description":"A map of Package objects indexed by Package.id","example":{"10":{"$ref":"#/components/examples/Package/value"}},"type":"object"},"state":{"description":"The current state of the index operation","example":"IndexFinished","type":"string"},"success":{"description":"A bool indicating succcessful index","example":true,"type":"boolean"}},"required":["manifest_hash","state","packages","distributions","environments","success","err"],"title":"IndexReport","type":"object"},"Layer":{"description":"A Layer within a Manifest and where Clair may retrieve it.","properties":{"hash":{"$ref":"#/components/schemas/Digest"},"headers":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"description":"map of arrays of header values keyed by header\nvalue. e.g. map[string][]string\n","type":"object"},"uri":{"description":"A URI describing where the layer may be found. Implementations\nMUST support http(s) schemes and MAY support additional\nschemes.\n","example":"https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36\n","type":"string"}},"required":["hash","uri","headers"],"title":"Layer","type":"object"},"Manifest":{"description":"A Manifest representing a container. The 'layers' array must\npreserve the original container's layer order for accurate usage.\n","properties":{"hash":{"$ref":"#/components/schemas/Digest"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"}},"required":["hash","layers"],"title":"Manifest","type":"object"},"Notification":{"description":"A notification expressing a change in a manifest affected by a vulnerability","properties":{"id":{"description":"a unique identifier for this notification","example":"5e4b387e-88d3-4364-86fd-063447a6fad2","type":"string"},"manifest":{"description":"the hash of the manifest affected by the provided vulnerability","example":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","type":"string"},"reason":{"description":"the reason for the notifcation, [added | removed]","example":"added","type":"string"},"vulnerability":{"$ref":"#/components/schemas/VulnSummary"}},"title":"Notification","type":"object"},"Package":{"description":"A package discovered by indexing a Manifest","example":{"$ref":"#/components/examples/Package/value"},"properties":{"arch":{"description":"The package's target system architecture","type":"string"},"cpe":{"description":"A CPE identifying the package","type":"string"},"id":{"description":"A unique ID representing this package","type":"string"},"kind":{"description":"Kind of package. Source | Binary","type":"string"},"module":{"description":"A module further defining a namespace for a package","type":"string"},"name":{"description":"Name of the Package","type":"string"},"normalized_version":{"$ref":"#/components/schemas/Version"},"source":{"$ref":"#/components/schemas/SourcePackage"},"version":{"description":"Version of the Package","type":"string"}},"required":["id","name","version"],"title":"Package","type":"object"},"Page":{"description":"A page object indicating to the client how to retrieve multiple pages of a particular entity.","properties":{"next":{"description":"The next id to submit to the api to continue paging","example":"1b4d0db2-e757-4150-bbbb-543658144205","type":"string"},"size":{"description":"The maximum number of elements in a page","example":1,"type":"int"}},"title":"Page"},"PagedNotifications":{"description":"A page object followed by a list of notifications","properties":{"notifications":{"description":"A list of notifications within this page","items":{"$ref":"#/components/schemas/Notification"},"type":"array"},"page":{"description":"A page object informing the client the next page to retrieve.\nIf page.next becomes \"-1\" the client should stop paging.\n","example":{"next":"1b4d0db2-e757-4150-bbbb-543658144205","size":100},"type":"object"}},"title":"PagedNotifications","type":"object"},"Repository":{"description":"A package repository","properties":{"cpe":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"}},"title":"Repository","type":"object"},"SourcePackage":{"description":"A source package affiliated with a Package","example":{"$ref":"#/components/examples/Package/value"},"properties":{"arch":{"type":"string"},"cpe":{"description":"A CPE identifying the package","type":"string"},"id":{"description":"A unique ID representing this package","type":"string"},"kind":{"description":"Kind of package. Source | Binary","type":"string"},"module":{"type":"string"},"name":{"description":"Name of the Package","type":"string"},"normalized_version":{"$ref":"#/components/schemas/Version"},"source":{"type":"string"},"version":{"description":"Version of the Package","type":"string"}},"required":["id","name","version"],"title":"SourcePackage","type":"object"},"State":{"description":"an opaque identifier","example":{"state":"aae368a064d7c5a433d0bf2c4f5554cc"},"properties":{"state":{"description":"an opaque identifier","type":"string"}},"required":["state"],"title":"State","type":"object"},"Version":{"description":"Version is a normalized claircore version, composed of a \"kind\" and an\narray of integers such that two versions of the same kind have the\ncorrect ordering when the integers are compared pair-wise.\n","example":"pep440:0.0.0.0.0.0.0.0.0","title":"Version","type":"string"},"VulnSummary":{"description":"A summary of a vulnerability","properties":{"description":{"description":"the vulnerability name","example":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","type":"string"},"distribution":{"$ref":"#/components/schemas/Distribution"},"fixed_in_version":{"description":"the version at which the vulnerability is fixed in. empty if not fixed.","example":"v0.0.1","type":"string"},"links":{"description":"links to external information about vulnerability","example":"http://link-to-advisory","type":"string"},"name":{"description":"the vulnerability name","example":"CVE-2009-5155","type":"string"},"normalized_severity":{"description":"A well defined set of severity strings guaranteed to be present.\n","enum":["Unknown","Negligible","Low","Medium","High","Critical","Defcon1"],"type":"string"},"package":{"$ref":"#/components/schemas/Package"},"repository":{"$ref":"#/components/schemas/Repository"}},"title":"VulnSummary","type":"object"},"Vulnerability":{"description":"A unique vulnerability indexed by Clair","example":{"$ref":"#/components/examples/Vulnerability/value"},"properties":{"description":{"description":"A description of this specific vulnerability.","type":"string"},"distribution":{"$ref":"#/components/schemas/Distribution"},"fixed_in_version":{"description":"A unique ID representing this vulnerability.","type":"string"},"id":{"description":"A unique ID representing this vulnerability.","type":"string"},"issued":{"description":"The timestamp in which the vulnerability was issued\n","type":"string"},"links":{"description":"A space separate list of links to any external information.\n","type":"string"},"name":{"description":"Name of this specific vulnerability.","type":"string"},"normalized_severity":{"description":"A well defined set of severity strings guaranteed to be present.\n","enum":["Unknown","Negligible","Low","Medium","High","Critical","Defcon1"],"type":"string"},"package":{"$ref":"#/components/schemas/Package"},"range":{"description":"The range of package versions that are affected by this vulnerability\n","type":"string"},"repository":{"$ref":"#/components/schemas/Repository"},"severity":{"description":"A severity keyword taken verbatim from the vulnerability source.\n","type":"string"},"updater":{"description":"A unique ID representing this vulnerability.","type":"string"}},"required":["id","updater","name","description","links","severity","normalized_severity","fixed_in_version"],"title":"Vulnerability","type":"object"},"VulnerabilityReport":{"description":"A report expressing discovered packages, package environments,\nand package vulnerabilities within a Manifest.\n","properties":{"distributions":{"additionalProperties":{"$ref":"#/components/schemas/Distribution"},"description":"A map of Distribution objects indexed by Distribution.id.\n","example":{"1":{"$ref":"#/components/examples/Distribution/value"}},"type":"object"},"environments":{"additionalProperties":{"items":{"$ref":"#/components/schemas/Environment"},"type":"array"},"description":"A mapping of Environment lists indexed by Package.id","example":{"10":[{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}]},"type":"object"},"manifest_hash":{"$ref":"#/components/schemas/Digest"},"package_vulnerabilities":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"description":"A mapping of Vulnerability.id lists indexed by Package.id.\n","example":{"10":["356835"]}},"packages":{"additionalProperties":{"$ref":"#/components/schemas/Package"},"description":"A map of Package objects indexed by Package.id","example":{"10":{"$ref":"#/components/examples/Package/value"}},"type":"object"},"vulnerabilities":{"additionalProperties":{"$ref":"#/components/schemas/Vulnerability"},"description":"A map of Vulnerabilities indexed by Vulnerability.id","example":{"356835":{"$ref":"#/components/examples/Vulnerability/value"}},"type":"object"}},"required":["manifest_hash","packages","distributions","environments","vulnerabilities","package_vulnerabilities"],"title":"VulnerabilityReport","type":"object"}}},"info":{"contact":{"email":"quay-devel@redhat.com","name":"Clair Team","url":"http://github.com/quay/clair"},"description":"ClairV4 is a set of cooperating microservices which scan, index, and\nmatch your container's content with known vulnerabilities.\n","license":{"name":"Apache License 2.0","url":"http://www.apache.org/licenses/"},"termsOfService":"","title":"ClairV4","version":"0.1"},"openapi":"3.0.2","paths":{"/api/v1/index_report":{"post":{"description":"By submitting a Manifest object to this endpoint Clair will fetch the\nlayers, scan each layer's contents, and provide an index of discovered\npackages, repository and distribution information.\n","operationId":"Index","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Manifest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexReport"}}},"description":"IndexReport Created"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Index the contents of a Manifest","tags":["Indexer"]}},"/api/v1/index_report/{manifest_hash}":{"get":{"description":"Given a Manifest's content addressable hash an IndexReport will\nbe retrieved if exists.\n","operationId":"GetIndexReport","parameters":[{"description":"A digest of a manifest that has been indexed previous to this\nrequest.\n","in":"path","name":"manifest_hash","required":true,"schema":{"$ref":"#/components/schemas/Digest"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexReport"}}},"description":"IndexReport retrieved"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retrieve an IndexReport for the given Manifest hash if exists.","tags":["Indexer"]}},"/api/v1/index_state":{"get":{"description":"The index state endpoint returns a json structure indicating the\nindexer's internal configuration state.\n\nA client may be interested in this as a signal that manifests may need\nto be re-indexed.\n","operationId":"IndexState","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/State"}}},"description":"Indexer State","headers":{"Etag":{"description":"Entity Tag","schema":{"type":"string"}}}},"304":{"description":"Indexer State Unchanged"}},"summary":"Report the indexer's internal configuration and state.","tags":["Indexer"]}},"/api/v1/notification/{notification_id}":{"delete":{"description":"Issues a delete of the provided notification id and all associated notifications.\nAfter this delete clients will no longer be able to retrieve notifications.\n","operationId":"notifications delete","parameters":[{"description":"A notification ID returned by a callback","in":"path","name":"notification_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"status":"ok"}}},"description":"OK"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"tags":["Notifier"]},"get":{"description":"By performing a GET with a notification_id as a path parameter the client\nwill retrieve a paginated response of notifcation objects\n","operationId":"notifications get","parameters":[{"description":"A notification ID returned by a callback","in":"path","name":"notification_id","schema":{"type":"string"}},{"description":"The maximum number of notifications to deliver in a single page.","in":"query","name":"page_size","schema":{"type":"int"}},{"description":"The next page to fetch via id. Typically this number is provided to you \non initial response in the page.next field.\nThe first GET request may omit this field.\n","in":"query","name":"next","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedNotifications"}}},"description":"A paginated list of notifications"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retreive a paginated result of notifications for the provided id","tags":["Notifier"]}},"/api/v1/vulnerability_report/{manifest_hash}":{"get":{"description":"Given a Manifest's content addressable hash a VulnerabilityReport\nwill be created. The Manifest **must** have been Indexed first\nvia the Index endpoint.\n","operationId":"GetVulnerabilityReport","parameters":[{"description":"A digest of a manifest that has been indexed previous to this\nrequest.\n","in":"path","name":"manifest_hash","required":true,"schema":{"$ref":"#/components/schemas/Digest"}}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VulnerabilityReport"}}},"description":"VulnerabilityReport Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retrieve a VulnerabilityReport for a given manifest's content\naddressable hash.\n","tags":["Matcher"]}}}}` - _openapiJSONEtag = `"c0d6d605030af646ed63e4ff5a6822406a2dca19dd251fdf297da0abecd89d64"` + _openapiJSON = `{"components":{"examples":{"Distribution":{"value":{"arch":"","cpe":"","did":"ubuntu","id":"1","name":"Ubuntu","pretty_name":"Ubuntu 18.04.3 LTS","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"}},"Environment":{"value":{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}},"Package":{"value":{"arch":"x86","cpe":"","id":"10","kind":"binary","module":"","name":"libapt-pkg5.0","normalized_version":"","source":{"id":"9","kind":"source","name":"apt","source":null,"version":"1.6.11"},"version":"1.6.11"}},"VulnSummary":{"value":{"description":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","dist":{"arch":"","cpe":"","did":"ubuntu","id":"0","name":"Ubuntu","pretty_name":"","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"},"fixed_in_version":"v0.0.1","links":"http://link-to-advisory","name":"CVE-2009-5155","normalized_severity":"Low","package":{"id":"0","kind":"","name":"glibc","package_db":"","repository_hint":"","source":null,"version":""},"repo":{"id":"0","key":"","name":"Ubuntu 18.04.3 LTS","uri":""}}},"Vulnerability":{"value":{"description":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","dist":{"arch":"","cpe":"","did":"ubuntu","id":"0","name":"Ubuntu","pretty_name":"","version":"18.04.3 LTS (Bionic Beaver)","version_code_name":"bionic","version_id":"18.04"},"fixed_in_version":"2.28-0ubuntu1","id":"356835","issued":"2019-10-12T07:20:50.52Z","links":"https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2009-5155\nhttp://people.canonical.com/~ubuntu-security/cve/2009/CVE-2009-5155.html\nhttps://sourceware.org/bugzilla/show_bug.cgi?id=11053\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=22793\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=32806\nhttps://debbugs.gnu.org/cgi/bugreport.cgi?bug=34238\nhttps://sourceware.org/bugzilla/show_bug.cgi?id=18986\"\n","name":"CVE-2009-5155","normalized_severity":"Low","package":{"id":"0","kind":"","name":"glibc","package_db":"","repository_hint":"","source":null,"version":""},"repo":{"id":"0","key":"","name":"Ubuntu 18.04.3 LTS","uri":""},"severity":"Low","updater":""}}},"responses":{"BadRequest":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Bad Request"},"InternalServerError":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Internal Server Error"},"MethodNotAllowed":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Method Not Allowed"},"NotFound":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"description":"Not Found"}},"schemas":{"Callback":{"description":"A callback for clients to retrieve notifications","properties":{"callback":{"description":"the url where notifications can be retrieved","example":"http://clair-notifier/notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643","type":"string"},"notification_id":{"description":"the unique identifier for this set of notifications","example":"269886f3-0146-4f08-9bf7-cb1138d48643","type":"string"}},"title":"Callback","type":"object"},"Digest":{"description":"A digest string with prefixed algorithm. The format is described here:\nhttps://github.com/opencontainers/image-spec/blob/master/descriptor.md#digests\n\nDigests are used throughout the API to identify Layers and Manifests.\n","example":"sha256:fc84b5febd328eccaa913807716887b3eb5ed08bc22cc6933a9ebf82766725e3","title":"Digest","type":"string"},"Distribution":{"description":"An indexed distribution discovered in a layer. See\nhttps://www.freedesktop.org/software/systemd/man/os-release.html\nfor explanations and example of fields.\n","example":{"$ref":"#/components/examples/Distribution/value"},"properties":{"arch":{"type":"string"},"cpe":{"type":"string"},"did":{"type":"string"},"id":{"description":"A unique ID representing this distribution","type":"string"},"name":{"type":"string"},"pretty_name":{"type":"string"},"version":{"type":"string"},"version_code_name":{"type":"string"},"version_id":{"type":"string"}},"required":["id","did","name","version","version_code_name","version_id","arch","cpe","pretty_name"],"title":"Distribution","type":"object"},"Environment":{"description":"The environment a particular package was discovered in.","properties":{"distribution_id":{"description":"The distribution ID found in an associated IndexReport or\nVulnerabilityReport.\n","example":"1","type":"string"},"introduced_in":{"$ref":"#/components/schemas/Digest"},"package_db":{"description":"The filesystem path or unique identifier of a package database.\n","example":"var/lib/dpkg/status","type":"string"}},"required":["package_db","introduced_in","distribution_id"],"title":"Environment","type":"object"},"Error":{"description":"A general error schema returned when status is not 200 OK","properties":{"code":{"description":"a code for this particular error","type":"string"},"message":{"description":"a message with further detail","type":"string"}},"title":"Error","type":"object"},"IndexReport":{"description":"A report of the Index process for a particular manifest. A\nclient's usage of this is largely information. Clair uses this\nreport for matching Vulnerabilities.\n","properties":{"distributions":{"additionalProperties":{"$ref":"#/components/schemas/Distribution"},"description":"A map of Distribution objects keyed by their Distribution.id\ndiscovered in the manifest.\n","example":{"1":{"$ref":"#/components/examples/Distribution/value"}},"type":"object"},"environments":{"additionalProperties":{"items":{"$ref":"#/components/schemas/Environment"},"type":"array"},"description":"A map of lists containing Environment objects keyed by the\nassociated Package.id.\n","example":{"10":[{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}]},"type":"object"},"err":{"description":"An error message on event of unsuccessful index","example":"","type":"string"},"manifest_hash":{"$ref":"#/components/schemas/Digest"},"packages":{"additionalProperties":{"$ref":"#/components/schemas/Package"},"description":"A map of Package objects indexed by Package.id","example":{"10":{"$ref":"#/components/examples/Package/value"}},"type":"object"},"state":{"description":"The current state of the index operation","example":"IndexFinished","type":"string"},"success":{"description":"A bool indicating succcessful index","example":true,"type":"boolean"}},"required":["manifest_hash","state","packages","distributions","environments","success","err"],"title":"IndexReport","type":"object"},"Layer":{"description":"A Layer within a Manifest and where Clair may retrieve it.","properties":{"hash":{"$ref":"#/components/schemas/Digest"},"headers":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"description":"map of arrays of header values keyed by header\nvalue. e.g. map[string][]string\n","type":"object"},"uri":{"description":"A URI describing where the layer may be found. Implementations\nMUST support http(s) schemes and MAY support additional\nschemes.\n","example":"https://storage.example.com/blob/2f077db56abccc19f16f140f629ae98e904b4b7d563957a7fc319bd11b82ba36\n","type":"string"}},"required":["hash","uri","headers"],"title":"Layer","type":"object"},"Manifest":{"description":"A Manifest representing a container. The 'layers' array must\npreserve the original container's layer order for accurate usage.\n","properties":{"hash":{"$ref":"#/components/schemas/Digest"},"layers":{"items":{"$ref":"#/components/schemas/Layer"},"type":"array"}},"required":["hash","layers"],"title":"Manifest","type":"object"},"Notification":{"description":"A notification expressing a change in a manifest affected by a vulnerability","properties":{"id":{"description":"a unique identifier for this notification","example":"5e4b387e-88d3-4364-86fd-063447a6fad2","type":"string"},"manifest":{"description":"the hash of the manifest affected by the provided vulnerability","example":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","type":"string"},"reason":{"description":"the reason for the notifcation, [added | removed]","example":"added","type":"string"},"vulnerability":{"$ref":"#/components/schemas/VulnSummary"}},"title":"Notification","type":"object"},"Package":{"description":"A package discovered by indexing a Manifest","example":{"$ref":"#/components/examples/Package/value"},"properties":{"arch":{"description":"The package's target system architecture","type":"string"},"cpe":{"description":"A CPE identifying the package","type":"string"},"id":{"description":"A unique ID representing this package","type":"string"},"kind":{"description":"Kind of package. Source | Binary","type":"string"},"module":{"description":"A module further defining a namespace for a package","type":"string"},"name":{"description":"Name of the Package","type":"string"},"normalized_version":{"$ref":"#/components/schemas/Version"},"source":{"$ref":"#/components/schemas/SourcePackage"},"version":{"description":"Version of the Package","type":"string"}},"required":["id","name","version"],"title":"Package","type":"object"},"Page":{"description":"A page object indicating to the client how to retrieve multiple pages of a particular entity.","properties":{"next":{"description":"The next id to submit to the api to continue paging","example":"1b4d0db2-e757-4150-bbbb-543658144205","type":"string"},"size":{"description":"The maximum number of elements in a page","example":1,"type":"int"}},"title":"Page"},"PagedNotifications":{"description":"A page object followed by a list of notifications","properties":{"notifications":{"description":"A list of notifications within this page","items":{"$ref":"#/components/schemas/Notification"},"type":"array"},"page":{"description":"A page object informing the client the next page to retrieve.\nIf page.next becomes \"-1\" the client should stop paging.\n","example":{"next":"1b4d0db2-e757-4150-bbbb-543658144205","size":100},"type":"object"}},"title":"PagedNotifications","type":"object"},"Repository":{"description":"A package repository","properties":{"cpe":{"type":"string"},"id":{"type":"string"},"key":{"type":"string"},"name":{"type":"string"},"uri":{"type":"string"}},"title":"Repository","type":"object"},"SourcePackage":{"description":"A source package affiliated with a Package","example":{"$ref":"#/components/examples/Package/value"},"properties":{"arch":{"type":"string"},"cpe":{"description":"A CPE identifying the package","type":"string"},"id":{"description":"A unique ID representing this package","type":"string"},"kind":{"description":"Kind of package. Source | Binary","type":"string"},"module":{"type":"string"},"name":{"description":"Name of the Package","type":"string"},"normalized_version":{"$ref":"#/components/schemas/Version"},"source":{"type":"string"},"version":{"description":"Version of the Package","type":"string"}},"required":["id","name","version"],"title":"SourcePackage","type":"object"},"State":{"description":"an opaque identifier","example":{"state":"aae368a064d7c5a433d0bf2c4f5554cc"},"properties":{"state":{"description":"an opaque identifier","type":"string"}},"required":["state"],"title":"State","type":"object"},"Version":{"description":"Version is a normalized claircore version, composed of a \"kind\" and an\narray of integers such that two versions of the same kind have the\ncorrect ordering when the integers are compared pair-wise.\n","example":"pep440:0.0.0.0.0.0.0.0.0","title":"Version","type":"string"},"VulnSummary":{"description":"A summary of a vulnerability","properties":{"description":{"description":"the vulnerability name","example":"In the GNU C Library (aka glibc or libc6) before 2.28,\nparse_reg_exp in posix/regcomp.c misparses alternatives,\nwhich allows attackers to cause a denial of service (assertion\nfailure and application exit) or trigger an incorrect result\nby attempting a regular-expression match.\"\n","type":"string"},"distribution":{"$ref":"#/components/schemas/Distribution"},"fixed_in_version":{"description":"the version at which the vulnerability is fixed in. empty if not fixed.","example":"v0.0.1","type":"string"},"links":{"description":"links to external information about vulnerability","example":"http://link-to-advisory","type":"string"},"name":{"description":"the vulnerability name","example":"CVE-2009-5155","type":"string"},"normalized_severity":{"description":"A well defined set of severity strings guaranteed to be present.\n","enum":["Unknown","Negligible","Low","Medium","High","Critical","Defcon1"],"type":"string"},"package":{"$ref":"#/components/schemas/Package"},"repository":{"$ref":"#/components/schemas/Repository"}},"title":"VulnSummary","type":"object"},"Vulnerability":{"description":"A unique vulnerability indexed by Clair","example":{"$ref":"#/components/examples/Vulnerability/value"},"properties":{"description":{"description":"A description of this specific vulnerability.","type":"string"},"distribution":{"$ref":"#/components/schemas/Distribution"},"fixed_in_version":{"description":"A unique ID representing this vulnerability.","type":"string"},"id":{"description":"A unique ID representing this vulnerability.","type":"string"},"issued":{"description":"The timestamp in which the vulnerability was issued\n","type":"string"},"links":{"description":"A space separate list of links to any external information.\n","type":"string"},"name":{"description":"Name of this specific vulnerability.","type":"string"},"normalized_severity":{"description":"A well defined set of severity strings guaranteed to be present.\n","enum":["Unknown","Negligible","Low","Medium","High","Critical","Defcon1"],"type":"string"},"package":{"$ref":"#/components/schemas/Package"},"range":{"description":"The range of package versions that are affected by this vulnerability\n","type":"string"},"repository":{"$ref":"#/components/schemas/Repository"},"severity":{"description":"A severity keyword taken verbatim from the vulnerability source.\n","type":"string"},"updater":{"description":"A unique ID representing this vulnerability.","type":"string"}},"required":["id","updater","name","description","links","severity","normalized_severity","fixed_in_version"],"title":"Vulnerability","type":"object"},"VulnerabilityReport":{"description":"A report expressing discovered packages, package environments,\nand package vulnerabilities within a Manifest.\n","properties":{"distributions":{"additionalProperties":{"$ref":"#/components/schemas/Distribution"},"description":"A map of Distribution objects indexed by Distribution.id.\n","example":{"1":{"$ref":"#/components/examples/Distribution/value"}},"type":"object"},"environments":{"additionalProperties":{"items":{"$ref":"#/components/schemas/Environment"},"type":"array"},"description":"A mapping of Environment lists indexed by Package.id","example":{"10":[{"distribution_id":"1","introduced_in":"sha256:35c102085707f703de2d9eaad8752d6fe1b8f02b5d2149f1d8357c9cc7fb7d0a","package_db":"var/lib/dpkg/status"}]},"type":"object"},"manifest_hash":{"$ref":"#/components/schemas/Digest"},"package_vulnerabilities":{"additionalProperties":{"items":{"type":"string"},"type":"array"},"description":"A mapping of Vulnerability.id lists indexed by Package.id.\n","example":{"10":["356835"]}},"packages":{"additionalProperties":{"$ref":"#/components/schemas/Package"},"description":"A map of Package objects indexed by Package.id","example":{"10":{"$ref":"#/components/examples/Package/value"}},"type":"object"},"vulnerabilities":{"additionalProperties":{"$ref":"#/components/schemas/Vulnerability"},"description":"A map of Vulnerabilities indexed by Vulnerability.id","example":{"356835":{"$ref":"#/components/examples/Vulnerability/value"}},"type":"object"}},"required":["manifest_hash","packages","distributions","environments","vulnerabilities","package_vulnerabilities"],"title":"VulnerabilityReport","type":"object"}}},"info":{"contact":{"email":"quay-devel@redhat.com","name":"Clair Team","url":"http://github.com/quay/clair"},"description":"ClairV4 is a set of cooperating microservices which scan, index, and\nmatch your container's content with known vulnerabilities.\n","license":{"name":"Apache License 2.0","url":"http://www.apache.org/licenses/"},"termsOfService":"","title":"ClairV4","version":"0.1"},"openapi":"3.0.2","paths":{"indexer/api/v1/index_report":{"post":{"description":"By submitting a Manifest object to this endpoint Clair will fetch the\nlayers, scan each layer's contents, and provide an index of discovered\npackages, repository and distribution information.\n","operationId":"Index","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Manifest"}}},"required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexReport"}}},"description":"IndexReport Created"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Index the contents of a Manifest","tags":["Indexer"]}},"indexer/api/v1/index_report/{manifest_hash}":{"get":{"description":"Given a Manifest's content addressable hash an IndexReport will\nbe retrieved if exists.\n","operationId":"GetIndexReport","parameters":[{"description":"A digest of a manifest that has been indexed previous to this\nrequest.\n","in":"path","name":"manifest_hash","required":true,"schema":{"$ref":"#/components/schemas/Digest"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IndexReport"}}},"description":"IndexReport retrieved"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retrieve an IndexReport for the given Manifest hash if exists.","tags":["Indexer"]}},"indexer/api/v1/index_state":{"get":{"description":"The index state endpoint returns a json structure indicating the\nindexer's internal configuration state.\n\nA client may be interested in this as a signal that manifests may need\nto be re-indexed.\n","operationId":"IndexState","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/State"}}},"description":"Indexer State","headers":{"Etag":{"description":"Entity Tag","schema":{"type":"string"}}}},"304":{"description":"Indexer State Unchanged"}},"summary":"Report the indexer's internal configuration and state.","tags":["Indexer"]}},"matcher/api/v1/vulnerability_report/{manifest_hash}":{"get":{"description":"Given a Manifest's content addressable hash a VulnerabilityReport\nwill be created. The Manifest **must** have been Indexed first\nvia the Index endpoint.\n","operationId":"GetVulnerabilityReport","parameters":[{"description":"A digest of a manifest that has been indexed previous to this\nrequest.\n","in":"path","name":"manifest_hash","required":true,"schema":{"$ref":"#/components/schemas/Digest"}}],"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VulnerabilityReport"}}},"description":"VulnerabilityReport Created"},"400":{"$ref":"#/components/responses/BadRequest"},"404":{"$ref":"#/components/responses/NotFound"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retrieve a VulnerabilityReport for a given manifest's content\naddressable hash.\n","tags":["Matcher"]}},"notifier/api/v1/notification/{notification_id}":{"delete":{"description":"Issues a delete of the provided notification id and all associated notifications.\nAfter this delete clients will no longer be able to retrieve notifications.\n","operationId":"notifications delete","parameters":[{"description":"A notification ID returned by a callback","in":"path","name":"notification_id","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"status":"ok"}}},"description":"OK"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"tags":["Notifier"]},"get":{"description":"By performing a GET with a notification_id as a path parameter the client\nwill retrieve a paginated response of notifcation objects\n","operationId":"notifications get","parameters":[{"description":"A notification ID returned by a callback","in":"path","name":"notification_id","schema":{"type":"string"}},{"description":"The maximum number of notifications to deliver in a single page.","in":"query","name":"page_size","schema":{"type":"int"}},{"description":"The next page to fetch via id. Typically this number is provided to you \non initial response in the page.next field.\nThe first GET request may omit this field.\n","in":"query","name":"next","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PagedNotifications"}}},"description":"A paginated list of notifications"},"400":{"$ref":"#/components/responses/BadRequest"},"405":{"$ref":"#/components/responses/MethodNotAllowed"},"500":{"$ref":"#/components/responses/InternalServerError"}},"summary":"Retreive a paginated result of notifications for the provided id","tags":["Notifier"]}}}}` + _openapiJSONEtag = `"320da4cfc6a78e15f7b856644aa7a1bca9bc2dc010133db3ea44df500e36af6e"` ) diff --git a/httptransport/notificationshandler_test.go b/httptransport/notificationshandler_test.go index 0c8fc0f129..a039caa610 100644 --- a/httptransport/notificationshandler_test.go +++ b/httptransport/notificationshandler_test.go @@ -41,7 +41,7 @@ func testNotificationHandlerDelete(t *testing.T) { h := NotificationHandler(nm) rr := httptest.NewRecorder() - u, _ := url.Parse("http://clair-notifier/api/v1/notification/" + noteID.String()) + u, _ := url.Parse("http://clair-notifier/notifier/api/v1/notification/" + noteID.String()) req := &http.Request{ URL: u, Method: http.MethodGet, @@ -86,7 +86,7 @@ func testNotificationHandlerGet(t *testing.T) { h := NotificationHandler(nm) rr := httptest.NewRecorder() - u, _ := url.Parse("http://clair-notifier/api/v1/notification/" + noteID.String()) + u, _ := url.Parse("http://clair-notifier/notifier/api/v1/notification/" + noteID.String()) req := &http.Request{ URL: u, Method: http.MethodGet, @@ -145,7 +145,7 @@ func testNotificationHandlerGetParams(t *testing.T) { h := NotificationHandler(nm) rr := httptest.NewRecorder() - u, _ := url.Parse("http://clair-notifier/api/v1/notification/" + noteID.String()) + u, _ := url.Parse("http://clair-notifier/notifier/api/v1/notification/" + noteID.String()) v := url.Values{} v.Set("page_size", pageSizeParam) v.Set("page", pageParam) diff --git a/httptransport/server.go b/httptransport/server.go index c47909ec5c..3f0b3edb41 100644 --- a/httptransport/server.go +++ b/httptransport/server.go @@ -19,17 +19,20 @@ import ( const ( apiRoot = "/api/v1/" + indexerRoot = "/indexer" + matcherRoot = "/matcher" + notifierRoot = "/notifier" internalRoot = apiRoot + "internal/" - VulnerabilityReportPath = apiRoot + "vulnerability_report/" - IndexAPIPath = apiRoot + "index_report" - IndexReportAPIPath = apiRoot + "index_report/" - IndexStateAPIPath = apiRoot + "index_state" - NotificationAPIPath = apiRoot + "notification/" - KeysAPIPath = apiRoot + "services/notifier/keys" - KeyByIDAPIPath = apiRoot + "services/notifier/keys/" - AffectedManifestAPIPath = internalRoot + "affected_manifest/" - UpdateOperationAPIPath = internalRoot + "update_operation/" - UpdateDiffAPIPath = internalRoot + "update_diff/" + IndexAPIPath = indexerRoot + apiRoot + "index_report" + IndexReportAPIPath = indexerRoot + apiRoot + "index_report/" + IndexStateAPIPath = indexerRoot + apiRoot + "index_state" + AffectedManifestAPIPath = indexerRoot + internalRoot + "affected_manifest/" + VulnerabilityReportPath = matcherRoot + apiRoot + "vulnerability_report/" + UpdateOperationAPIPath = matcherRoot + internalRoot + "update_operation/" + UpdateDiffAPIPath = matcherRoot + internalRoot + "update_diff/" + NotificationAPIPath = notifierRoot + apiRoot + "notification/" + KeysAPIPath = notifierRoot + apiRoot + "services/notifier/keys" + KeyByIDAPIPath = notifierRoot + apiRoot + "services/notifier/keys/" OpenAPIV1Path = "/openapi/v1" ) diff --git a/local-dev/clair/config.yaml b/local-dev/clair/config.yaml index 3e95629f1a..ae69cc81d8 100644 --- a/local-dev/clair/config.yaml +++ b/local-dev/clair/config.yaml @@ -22,7 +22,7 @@ notifier: poll_interval: 15s # webhook: # target: "http://webhook/" - # callback: "http://clair-notifier/api/v1/notifications" + # callback: "http://clair-notifier/notifier/api/v1/notifications" amqp: direct: true exchange: @@ -32,7 +32,7 @@ notifier: auto_delete: false uris: ["amqp://guest:guest@clair-rabbitmq:5672/"] routing_key: "notifications" - callback: "http://clair-notifier/api/v1/notifications" + callback: "http://clair-notifier/notifier/api/v1/notifications" # tracing and metrics config trace: name: "jaeger" diff --git a/notifier/amqp/deliverer_integration_test.go b/notifier/amqp/deliverer_integration_test.go index a5dd47901d..7a2935873d 100644 --- a/notifier/amqp/deliverer_integration_test.go +++ b/notifier/amqp/deliverer_integration_test.go @@ -22,7 +22,7 @@ const ( func TestDeliverer(t *testing.T) { integration.Skip(t) const ( - callback = "http://clair-notifier/api/v1/notifications" + callback = "http://clair-notifier/notifier/api/v1/notifications" ) var ( uri = os.Getenv("RABBITMQ_CONNECTION_STRING") diff --git a/notifier/stomp/deliverer_integration_test.go b/notifier/stomp/deliverer_integration_test.go index 133fbc92f1..ce79003f65 100644 --- a/notifier/stomp/deliverer_integration_test.go +++ b/notifier/stomp/deliverer_integration_test.go @@ -23,7 +23,7 @@ func TestDeliverer(t *testing.T) { ctx, done := log.TestLogger(context.Background(), t) defer done() const ( - callback = "http://clair-notifier/api/v1/notifications" + callback = "http://clair-notifier/notifier/api/v1/notifications" ) var ( diff --git a/notifier/webhook/deliverer_test.go b/notifier/webhook/deliverer_test.go index f8532a133f..db6e54cfb3 100644 --- a/notifier/webhook/deliverer_test.go +++ b/notifier/webhook/deliverer_test.go @@ -25,7 +25,7 @@ import ( ) var ( - callback = "http://clair-notifier/api/v1/notification" + callback = "http://clair-notifier/notifier/api/v1/notification" noteID = uuid.New() ) diff --git a/openapi.yaml b/openapi.yaml index 522fa40ec3..82526547d7 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -16,7 +16,7 @@ info: url: "http://www.apache.org/licenses/" paths: - /api/v1/notification/{notification_id}: + notifier/api/v1/notification/{notification_id}: delete: tags: - Notifier @@ -84,7 +84,7 @@ paths: 500: $ref: '#/components/responses/InternalServerError' - /api/v1/index_report: + indexer/api/v1/index_report: post: tags: - Indexer @@ -114,7 +114,7 @@ paths: 500: $ref: '#/components/responses/InternalServerError' - /api/v1/index_report/{manifest_hash}: + indexer/api/v1/index_report/{manifest_hash}: get: tags: - Indexer @@ -148,7 +148,7 @@ paths: 500: $ref: '#/components/responses/InternalServerError' - /api/v1/vulnerability_report/{manifest_hash}: + matcher/api/v1/vulnerability_report/{manifest_hash}: get: tags: - Matcher @@ -184,7 +184,7 @@ paths: $ref: '#/components/responses/MethodNotAllowed' 500: $ref: '#/components/responses/InternalServerError' - '/api/v1/index_state': + indexer/api/v1/index_state: get: tags: - Indexer @@ -405,7 +405,7 @@ components: callback: description: "the url where notifications can be retrieved" type: string - example: "http://clair-notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643" + example: "http://clair-notifier/notifier/api/v1/notifications/269886f3-0146-4f08-9bf7-cb1138d48643" VulnSummary: title: VulnSummary