Skip to content

Commit

Permalink
Merge pull request #75 from SantiagoTorres/master
Browse files Browse the repository at this point in the history
server: add in-toto support
  • Loading branch information
06kellyjac authored Nov 15, 2019
2 parents c3c5d4c + 099f718 commit 0bf43f1
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 7 deletions.
20 changes: 20 additions & 0 deletions Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion cmd/kubesec/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ import (
)

func init() {
// FIXME: I don't understand why I need a reference to keypath here,
// and the cobra docs don't make it exactly clear.
var keypath string
httpCmd.Flags().StringVarP(&keypath, "keypath", "k", "", "Path to in-toto link signing key")
rootCmd.AddCommand(httpCmd)
}

Expand All @@ -33,7 +37,9 @@ var httpCmd = &cobra.Command{
stopCh := server.SetupSignalHandler()
jsonLogger, _ := NewLogger("info", "json")

server.ListenAndServe(port, time.Minute, jsonLogger, stopCh)
keypath := cmd.Flag("keypath").Value.String()

server.ListenAndServe(port, time.Minute, jsonLogger, stopCh, keypath)
return nil
},
}
34 changes: 34 additions & 0 deletions pkg/ruler/ruleset.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package ruler

import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/controlplaneio/kubesec/pkg/rules"
"github.com/garethr/kubeval/kubeval"
"github.com/ghodss/yaml"
"github.com/in-toto/in-toto-golang/in_toto"
"github.com/thedevsaddam/gojsonq"
"go.uber.org/zap"
"runtime"
Expand Down Expand Up @@ -271,6 +273,38 @@ func (rs *Ruleset) Run(fileBytes []byte) ([]Report, error) {
return reports, nil
}

func GenerateInTotoLink(reports []Report, fileBytes []byte) in_toto.Metablock {

var linkMb in_toto.Metablock

materials := make(map[string]interface{})
request := make(map[string]interface{})
request["sha256"] = fmt.Sprintf("%x", sha256.Sum256([]uint8(fileBytes)))
materials["request"] = request

products := make(map[string]interface{})
for _, report := range reports {
reportArtifact := make(map[string]interface{})
// FIXME: encoding as json now for integrity check, this is the wrong way
// to compute the hash over the result. Also, some error checking would be
// more than ideal.
reportValue, _ := json.Marshal(report)
reportArtifact["sha256"] =
fmt.Sprintf("%x", sha256.Sum256([]uint8(reportValue)))
products[report.Object] = reportArtifact
}

linkMb.Signatures = []in_toto.Signature{}
linkMb.Signed = in_toto.Link{
Type: "link",
Name: "kubesec",
Materials: materials,
Products: products,
}

return linkMb
}

func (rs *Ruleset) generateReport(json []byte) Report {
report := Report{
Object: "Unknown",
Expand Down
51 changes: 50 additions & 1 deletion pkg/ruler/ruleset_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ruler

import (
"github.com/ghodss/yaml"
"github.com/in-toto/in-toto-golang/in_toto"
"go.uber.org/zap"
"strings"
"testing"
Expand Down Expand Up @@ -66,7 +67,7 @@ kind: Deployment
spec:
template:
spec:
hostNetwork:
hostNetwork:
initContainers:
- name: init1
containers:
Expand Down Expand Up @@ -167,3 +168,51 @@ data:
t.Errorf("Got error %v ", report.Message)
}
}

func TestRuleset_Get_intoto(t *testing.T) {
var data = `
---
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
hostNetwork: true
initContainers:
- name: init1
securityContext:
readOnlyRootFilesystem: true
- name: init2
securityContext:
readOnlyRootFilesystem: false
- name: init3
containers:
- name: c1
- name: c2
securityContext:
readOnlyRootFilesystem: false
runAsNonRoot: true
runAsUser: 1001
- name: c3
securityContext:
readOnlyRootFilesystem: true
`

json, err := yaml.YAMLToJSON([]byte(data))
if err != nil {
t.Fatal(err.Error())
}

var reports []Report

report := NewRuleset(zap.NewNop().Sugar()).generateReport(json)
reports = append(reports, report)

link := GenerateInTotoLink(reports, []byte(data)).Signed.(in_toto.Link)

if len(link.Materials) < 1 || len(link.Products) < 1 {
t.Errorf("Should have generated a report with at least one material and a product %+v",
link)
}
}
45 changes: 40 additions & 5 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"encoding/json"
"github.com/controlplaneio/kubesec/pkg/ruler"
"github.com/in-toto/in-toto-golang/in_toto"
"go.uber.org/zap"
"io/ioutil"
"net/http"
Expand All @@ -17,10 +18,10 @@ import (
)

// ListenAndServe starts a web server and waits for SIGTERM
func ListenAndServe(port string, timeout time.Duration, logger *zap.SugaredLogger, stopCh <-chan struct{}) {
func ListenAndServe(port string, timeout time.Duration, logger *zap.SugaredLogger, stopCh <-chan struct{}, keypath string) {
mux := http.DefaultServeMux
mux.Handle("/", scanHandler(logger))
mux.Handle("/scan", scanHandler(logger))
mux.Handle("/", scanHandler(logger, keypath))
mux.Handle("/scan", scanHandler(logger, keypath))
mux.Handle("/metrics", promhttp.Handler())
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
Expand Down Expand Up @@ -80,13 +81,20 @@ func PrettyJSON(b []byte) string {
return string(out.Bytes())
}

func scanHandler(logger *zap.SugaredLogger) http.Handler {
func scanHandler(logger *zap.SugaredLogger, keypath string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
http.Redirect(w, r, "https://kubesec.io", http.StatusSeeOther)
return
}

// fail early if no in-toto signing key is configured for this server
if r.URL.Query().Get("in-toto") != "" && keypath == "" {
logger.Errorf("Attempted to serve an in-toto payload but no key is available")
w.WriteHeader(http.StatusInternalServerError)
return
}

body, err := ioutil.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
Expand All @@ -95,14 +103,41 @@ func scanHandler(logger *zap.SugaredLogger) http.Handler {
}
defer r.Body.Close()

var payload interface{}
reports, err := ruler.NewRuleset(logger).Run(body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(err.Error() + "\n"))
return
}

res, err := json.Marshal(reports)
if r.URL.Query().Get("in-toto") != "" {
json_key, err := ioutil.ReadFile(keypath)
if err != nil {
logger.Errorf("Attempted to serve an in-toto payload but the key is unavailable: %v",
err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}
key, err := in_toto.ParseEd25519FromPrivateJSON(string(json_key))
if err != nil {
logger.Errorf("Attempted to serve an in-toto payload but the key is unavailable: %v",
err.Error())
w.WriteHeader(http.StatusInternalServerError)
return
}

link := ruler.GenerateInTotoLink(reports, body)
link.Sign(key)
payload = map[string]interface{}{
"reports": reports,
"link": link,
}
} else {
payload = reports
}

res, err := json.Marshal(payload)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
Expand Down

0 comments on commit 0bf43f1

Please sign in to comment.