diff --git a/images/validate-image/Dockerfile b/images/validate-image/Dockerfile new file mode 100644 index 00000000000..c0643fa556f --- /dev/null +++ b/images/validate-image/Dockerfile @@ -0,0 +1,30 @@ +# Copyright 2019 The Knative 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. + +FROM golang:1.12.1 +RUN apt-get update + +ENV TEMP_REPO_DIR /go/src/knative.dev/test-infra +ENV TOOL_NAME validate-image + +# Temporarily add test-infra to the image to build custom tools +ADD ./ $TEMP_REPO_DIR + +RUN make -C $TEMP_REPO_DIR/tools/$TOOL_NAME/ +RUN cp $TEMP_REPO_DIR/tools/$TOOL_NAME/$TOOL_NAME /$TOOL_NAME + +# Remove test-infra from the container +RUN rm -fr $TEMP_REPO_DIR + +ENTRYPOINT ["/validate-image"] diff --git a/images/validate-image/Makefile b/images/validate-image/Makefile new file mode 100644 index 00000000000..0c6ae9f0740 --- /dev/null +++ b/images/validate-image/Makefile @@ -0,0 +1,16 @@ +# Copyright 2019 The Knative 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. + +IMAGE_NAME = validate-image +include ../../shared/Makefile.simple-image diff --git a/tools/validate-image/Makefile b/tools/validate-image/Makefile new file mode 100644 index 00000000000..9d5be3317f2 --- /dev/null +++ b/tools/validate-image/Makefile @@ -0,0 +1,16 @@ +# Copyright 2019 The Knative 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. + +all: + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build . diff --git a/tools/validate-image/gke_deployment/validate_service.yaml b/tools/validate-image/gke_deployment/validate_service.yaml new file mode 100644 index 00000000000..1b86219afae --- /dev/null +++ b/tools/validate-image/gke_deployment/validate_service.yaml @@ -0,0 +1,72 @@ +# Copyright 2019 The Knative 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. + +apiVersion: v1 +kind: Namespace +metadata: + name: validate-image +--- +apiVersion: v1 +kind: Service +metadata: + name: validate-image-service + namespace: validate-image + labels: + app: validate-image +spec: + type: LoadBalancer + ports: + - port: 80 + targetPort: http-server + selector: + app: validate-image +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: validate-image-app + namespace: validate-image + labels: + app: validate-image +spec: + template: + metadata: + labels: + app: validate-image + spec: + containers: + - name: validate-image-app + image: gcr.io/knative-tests/test-infra/validate-image:latest + command: ["/validate-image"] + env: + - name: GOOGLE_APPLICATION_CREDENTIALS + value: /secrets/google-app-credential/knative-monitoring-credential.json + imagePullPolicy: Always + ports: + - name: http-server + containerPort: 8080 + volumeMounts: + - name: sender-email-credentials + mountPath: /secrets/sender-email + readOnly: true + - name: google-app-credentials + mountPath: /secrets/google-app-credential/ + readOnly: true + volumes: + - name: sender-email-credentials + secret: + secretName: sender-email-credentials + - name: google-app-credentials + secret: + secretName: google-app-credentials diff --git a/tools/validate-image/main.go b/tools/validate-image/main.go new file mode 100644 index 00000000000..b6451b609be --- /dev/null +++ b/tools/validate-image/main.go @@ -0,0 +1,62 @@ +// Copyright 2019 The Knative 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. + +package main + +import ( + "flag" + "fmt" + "log" + "net/http" + "os" + + "knative.dev/test-infra/tools/monitoring/mail" +) + +func main() { + mailAddrSF := flag.String("sender-email", "/secrets/sender-email/mail", "Alert sender email address file") + mailPassSF := flag.String("sender-password", "/secrets/sender-email/password", "Alert sender email password file") + flag.Parse() + + mailConfig, err := mail.NewMailConfig(*mailAddrSF, *mailPassSF) + if err != nil { + log.Fatal(err) + } + + imageClient, err := NewValidateImageClient(mailConfig) + if err != nil { + log.Fatalf("Failed to create ValidateImageClient. Error: %v\n", err) + } + imageClient.Run() + + // use PORT environment variable, or default to 8080 + port := "8080" + if fromEnv := os.Getenv("PORT"); fromEnv != "" { + port = fromEnv + } + + // register hello function to handle all requests + server := http.NewServeMux() + server.HandleFunc("/validate-image", validateImage) + + // start the web server on port and accept requests + log.Printf("Server listening on port %s", port) + log.Fatal(http.ListenAndServe(":"+port, server)) +} + +func validateImage(w http.ResponseWriter, r *http.Request) { + log.Printf("Serving request: %s", r.URL.Path) + + fmt.Fprintf(w, "Last validate-image alert sent: %v\n", lastSent) +} diff --git a/tools/validate-image/valImage.go b/tools/validate-image/valImage.go new file mode 100644 index 00000000000..a3c523a48ba --- /dev/null +++ b/tools/validate-image/valImage.go @@ -0,0 +1,111 @@ +// Copyright 2019 The Knative 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. + +package main + +import ( + "context" + "encoding/json" + "fmt" + "log" + "time" + + "cloud.google.com/go/pubsub" + "knative.dev/test-infra/tools/monitoring/mail" + "knative.dev/test-infra/tools/monitoring/subscriber" +) + +var ( + // monitoringSubs is a list of subscriptions to subscribe for image vulnerabilities + monitoringSubs = [...]string{ + "sub-container-analysis-notes-v1beta1", + "sub-container-analysis-occurrences-v1beta1", + } + recipients = []string{"knative-productivity-dev@googlegroups.com"} + + // alertFreq is the minimum wait time before sending another image vulnerability alert + alertFreq = 24 * time.Hour + + // Cache the last alert time in memory to prevent multiple image + // vulnerability alerts sent in a short duration of time. + lastSent = time.Time{} +) + +// Client holds resources for monitoring image vulnerabilities +type Client struct { + subClients []*subscriber.Client + mailClient *mail.Config +} + +// NewValidateImageClient initialize all the resources for monitoring image vulnerabilities +func NewValidateImageClient(mconfig *mail.Config) (*Client, error) { + var subClients = make([]*subscriber.Client, 0) + + for _, sub := range monitoringSubs { + log.Printf("Appending sub: %v\n", sub) + subc, err := subscriber.NewSubscriberClient(sub) + if err != nil { + return nil, err + } + subClients = append(subClients, subc) + log.Printf("subclients: %v\n", subClients) + } + + return &Client{ + subClients: subClients, + mailClient: mconfig, + }, nil +} + +// Run start a background process that listens to the message +func (c *Client) Run() { + log.Println("Starting image vulnerabilities monitoring") + for _, sub := range c.subClients { + c.listen(sub) + } +} + +func (c *Client) listen(subClient *subscriber.Client) { + go func() { + err := subClient.Receive(context.Background(), func(ctx context.Context, msg *pubsub.Message) { + log.Printf("Message: %v\n", string(msg.Data)) + log.Printf("Pubsub Message: %v\n", msg) + + if time.Now().Sub(lastSent) > alertFreq { + err := c.mailClient.Send(recipients, "Image Vulnerabilities Detected", toMailContent(msg)) + if err != nil { + log.Printf("Failed to send alert message %v\n", err) + } else { + lastSent = time.Now() + } + } else { + log.Println("Message not sent because an alert is sent recently.") + } + msg.Ack() + }) + if err != nil { + log.Printf("Failed to receive messages due to: %v\n", err) + } + }() +} + +func toMailContent(msg *pubsub.Message) string { + c := fmt.Sprintf("Message Data: %v\n", string(msg.Data)) + if b, err := json.MarshalIndent(msg, "", "\t"); err == nil { + c += fmt.Sprintf("\nPubsub Message: %v\n", string(b)) + } + c += fmt.Sprintf("\nRaw Message: %+v\n", msg) + log.Printf("Mail Content:\n %v\n", c) + return c +}