diff --git a/Gopkg.lock b/Gopkg.lock index 9144c2a..663b893 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -707,6 +707,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9034d27880c6ae363c6940de468686ef5ed164def1436f555d53449f19574bdb" + inputs-digest = "b709a5361a6dc08a29f144881b58a761320f76f653982bf88082ab0d6027eb59" solver-name = "gps-cdcl" solver-version = 1 diff --git a/artifacts/simple-image/Dockerfile b/artifacts/simple-image/Dockerfile index f043f4d..aa2f437 100644 --- a/artifacts/simple-image/Dockerfile +++ b/artifacts/simple-image/Dockerfile @@ -13,5 +13,7 @@ # limitations under the License. FROM fedora +RUN mkdir -p /tmp/ ADD kube-provenance-apiserver / +ADD kube-apiserver-audit.log /tmp/ ENTRYPOINT ["/kube-provenance-apiserver"] diff --git a/artifacts/simple-image/kube-apiserver-audit.log b/artifacts/simple-image/kube-apiserver-audit.log new file mode 100644 index 0000000..ca6b93b --- /dev/null +++ b/artifacts/simple-image/kube-apiserver-audit.log @@ -0,0 +1,6 @@ +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:16:20Z"},"level":"Request","timestamp":"2018-08-05T00:16:20Z","auditID":"99b27a2b-e320-4ca7-94bf-9a3a080cb2f2","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses","verb":"create","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":201},"requestObject":{"apiVersion":"postgrescontroller.kubeplus/v1","kind":"Postgres","metadata":{"creationTimestamp":null,"name":"client25","namespace":"default"},"spec":{"databases":["moodle"],"deploymentName":"client25","image":"postgres:9.3","initcommands":["\\c moodle;","create table moodle_data1 (items varchar(250));","insert into moodle_data1 (items) values ('Moodle data1');","insert into moodle_data1 (items) values ('Moodle data1');","insert into moodle_data1 (items) values ('Moodle data2');","GRANT ALL PRIVILEGES ON TABLE moodle_data1 TO devdatta;","GRANT ALL PRIVILEGES ON TABLE moodle_data1 TO pallavi;"],"replicas":1,"users":[{"password":"pass123","username":"devdatta"},{"password":"pass234","username":"pallavi"}]}},"requestReceivedTimestamp":"2018-08-05T00:16:20.176744Z","stageTimestamp":"2018-08-05T00:16:20.180766Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:16:46Z"},"level":"Request","timestamp":"2018-08-05T00:16:46Z","auditID":"e20539e0-d8a6-40b8-88e0-2592f1f4ac0c","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses/client25","verb":"patch","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","name":"client25","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestObject":{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\",\"ecommerce\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass234\",\"username\":\"pallavi\"}]}}\n"}},"spec":{"databases":["moodle","wordpress","ecommerce"]}},"requestReceivedTimestamp":"2018-08-05T00:16:46.921618Z","stageTimestamp":"2018-08-05T00:16:46.923885Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:16:51Z"},"level":"Request","timestamp":"2018-08-05T00:16:51Z","auditID":"68551497-6eca-4d6a-afc7-16d5909bfd78","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses/client25","verb":"patch","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","name":"client25","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestObject":{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass234\",\"username\":\"pallavi\"}]}}\n"}},"spec":{"databases":["moodle","wordpress"]}},"requestReceivedTimestamp":"2018-08-05T00:16:51.839690Z","stageTimestamp":"2018-08-05T00:16:51.841691Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:17:12Z"},"level":"Request","timestamp":"2018-08-05T00:17:12Z","auditID":"fa44d2d5-f13e-4ff3-8e32-f4e693546f7a","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses/client25","verb":"patch","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","name":"client25","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestObject":{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass234\",\"username\":\"pallavi\"},{\"password\":\"pass1\",\"username\":\"shrinivas\"}]}}\n"}},"spec":{"users":[{"password":"pass123","username":"devdatta"},{"password":"pass234","username":"pallavi"},{"password":"pass1","username":"shrinivas"}]}},"requestReceivedTimestamp":"2018-08-05T00:17:12.759670Z","stageTimestamp":"2018-08-05T00:17:12.761817Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:17:20Z"},"level":"Request","timestamp":"2018-08-05T00:17:20Z","auditID":"0edc056d-c370-4f29-b41c-ee584fc54c53","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses/client25","verb":"patch","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","name":"client25","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestObject":{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass234\",\"username\":\"pallavi\"}]}}\n"}},"spec":{"users":[{"password":"pass123","username":"devdatta"},{"password":"pass234","username":"pallavi"}]}},"requestReceivedTimestamp":"2018-08-05T00:17:20.526592Z","stageTimestamp":"2018-08-05T00:17:20.528723Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} +{"kind":"Event","apiVersion":"audit.k8s.io/v1beta1","metadata":{"creationTimestamp":"2018-08-05T00:17:26Z"},"level":"Request","timestamp":"2018-08-05T00:17:26Z","auditID":"cf460fc3-a11a-4b8f-a393-5ae60f7d662b","stage":"ResponseComplete","requestURI":"/apis/postgrescontroller.kubeplus/v1/namespaces/default/postgreses/client25","verb":"patch","user":{"username":"system:admin","groups":["system:masters","system:authenticated"]},"sourceIPs":["127.0.0.1"],"userAgent":"kubectl/v1.12.0 (linux/amd64) kubernetes/f1b6611","objectRef":{"resource":"postgreses","namespace":"default","name":"client25","apiGroup":"postgrescontroller.kubeplus","apiVersion":"v1"},"responseStatus":{"metadata":{},"code":200},"requestObject":{"metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"postgrescontroller.kubeplus/v1\",\"kind\":\"Postgres\",\"metadata\":{\"annotations\":{},\"name\":\"client25\",\"namespace\":\"default\"},\"spec\":{\"databases\":[\"moodle\",\"wordpress\"],\"deploymentName\":\"client25\",\"image\":\"postgres:9.3\",\"replicas\":1,\"users\":[{\"password\":\"pass123\",\"username\":\"devdatta\"},{\"password\":\"pass123\",\"username\":\"pallavi\"}]}}\n"}},"spec":{"users":[{"password":"pass123","username":"devdatta"},{"password":"pass123","username":"pallavi"}]}},"requestReceivedTimestamp":"2018-08-05T00:17:26.047828Z","stageTimestamp":"2018-08-05T00:17:26.049674Z","annotations":{"authorization.k8s.io/decision":"allow","authorization.k8s.io/reason":""}} diff --git a/build-provenance-artifacts.sh b/build-provenance-artifacts.sh index 4c3f491..5536fff 100755 --- a/build-provenance-artifacts.sh +++ b/build-provenance-artifacts.sh @@ -3,5 +3,3 @@ export GOOS=linux; go build . cp kubeprovenance ./artifacts/simple-image/kube-provenance-apiserver docker build -t kube-provenance-apiserver:latest ./artifacts/simple-image - - diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index 1aca2e7..1d21065 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -4,13 +4,13 @@ import ( "fmt" "strings" + "github.com/emicklei/go-restful" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/version" genericapiserver "k8s.io/apiserver/pkg/server" - "github.com/emicklei/go-restful" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/serializer" utilruntime "k8s.io/apimachinery/pkg/util/runtime" @@ -21,11 +21,11 @@ const GroupName = "kubeprovenance.cloudark.io" const GroupVersion = "v1" var ( - Scheme = runtime.NewScheme() - Codecs = serializer.NewCodecFactory(Scheme) - SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) - AddToScheme = SchemeBuilder.AddToScheme - SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} + Scheme = runtime.NewScheme() + Codecs = serializer.NewCodecFactory(Scheme) + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + AddToScheme = SchemeBuilder.AddToScheme + SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: GroupVersion} ) func addKnownTypes(scheme *runtime.Scheme) error { @@ -40,7 +40,7 @@ func init() { utilruntime.Must(Scheme.SetVersionPriority(SchemeGroupVersion)) // TODO(devdattakulkarni) -- Following comments coming from sample-apiserver. - // Leaving them for now. + // Leaving them for now. // we need to add the options to empty v1 // TODO fix the server code to avoid this metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: GroupVersion}) @@ -191,12 +191,15 @@ func getHistory(request *restful.Request, response *restful.Response) { // /apis/kubeprovenance.cloudark.io/v1/namespaces/default/deployments/dep1/compositions resourcePathSlice := strings.Split(requestPath, "/") resourceKind := resourcePathSlice[6] // Kind is 7th element in the slice -// provenanceInfo := provenance.TotalClusterProvenance.GetProvenance(resourceKind, resourceName) + // provenanceInfo := provenance.TotalClusterProvenance.GetProvenance(resourceKind, resourceName) provenanceInfo := "Resource Name:" + resourceName + " Resource Kind:" + resourceKind - fmt.Println(provenanceInfo) - response.Write([]byte(provenanceInfo)) + specStrings := provenance.ObjectFullProvenance.SpecHistory() + for _, str := range specStrings { + response.Write([]byte(str)) + } + } func bisect(request *restful.Request, response *restful.Response) { @@ -210,7 +213,7 @@ func bisect(request *restful.Request, response *restful.Response) { // /apis/kubeprovenance.cloudark.io/v1/namespaces/default/deployments/dep1/compositions resourcePathSlice := strings.Split(requestPath, "/") resourceKind := resourcePathSlice[6] // Kind is 7th element in the slice - + var provenanceInfo string //provenanceInfo = provenance.TotalClusterProvenance.GetProvenance(resourceKind, resourceName) provenanceInfo = "Resource Name:" + resourceName + " Resource Kind:" + resourceKind @@ -219,14 +222,13 @@ func bisect(request *restful.Request, response *restful.Response) { field := request.QueryParameter("field") value := request.QueryParameter("value") - provenanceInfo = provenanceInfo + " Field:" + field + " Value:" + value + provenanceInfo = provenanceInfo + " Field:" + field + " Value:" + value fmt.Println("ProvenanceInfo:%v", provenanceInfo) response.Write([]byte(provenanceInfo)) } - func getDiff(request *restful.Request, response *restful.Response) { fmt.Println("Inside getDiff") resourceName := request.PathParameter("resource-id") @@ -244,7 +246,7 @@ func getDiff(request *restful.Request, response *restful.Response) { field := request.QueryParameter("field") var diffInfo string - if ( start == "" || end == "" ) { + if start == "" || end == "" { fmt.Println("Start:%s", start) fmt.Println("End:%s", end) diffInfo = "start and end query parameters missing\n" @@ -259,4 +261,4 @@ func getDiff(request *restful.Request, response *restful.Response) { diffInfo = "This is Diff Info: " + start + " " + end + " " + field } response.Write([]byte(diffInfo)) -} \ No newline at end of file +} diff --git a/pkg/provenance/provenance.go b/pkg/provenance/provenance.go index e643150..06fd537 100644 --- a/pkg/provenance/provenance.go +++ b/pkg/provenance/provenance.go @@ -1,41 +1,47 @@ -package provenance +package provenance import ( + "bufio" "encoding/json" - "os" - "time" "fmt" - "strings" "io/ioutil" - "log" "net/http" - "net/url" - cert "crypto/x509" - "crypto/tls" - "context" - "gopkg.in/yaml.v2" - "github.com/coreos/etcd/client" + "os" + "strings" + + yaml "gopkg.in/yaml.v2" + "k8s.io/apiserver/pkg/apis/audit/v1beta1" ) var ( - serviceHost string - servicePort string - Namespace string - httpMethod string + serviceHost string + servicePort string + Namespace string + httpMethod string etcdServiceURL string - KindPluralMap map[string]string + KindPluralMap map[string]string kindVersionMap map[string]string compositionMap map[string][]string - REPLICA_SET string - DEPLOYMENT string - POD string - CONFIG_MAP string - SERVICE string + REPLICA_SET string + DEPLOYMENT string + POD string + CONFIG_MAP string + SERVICE string ETCD_CLUSTER string + + ObjectFullProvenance Object ) +type Event v1beta1.Event + +//for example a postgres +type Object map[int]Spec +type Spec struct { + attributeToData map[string]string +} + func init() { serviceHost = os.Getenv("KUBERNETES_SERVICE_HOST") servicePort = os.Getenv("KUBERNETES_SERVICE_PORT") @@ -53,460 +59,250 @@ func init() { KindPluralMap = make(map[string]string) kindVersionMap = make(map[string]string) - compositionMap = make(map[string][]string,0) + compositionMap = make(map[string][]string, 0) } func CollectProvenance() { - fmt.Println("Inside CollectProvenance") - for { - readKindCompositionFile() - provenanceToPrint := false - resourceKindList := getResourceKinds() - for _, resourceKind := range resourceKindList { - topLevelMetaDataOwnerRefList := getResourceNames(resourceKind) - for _, topLevelObject := range topLevelMetaDataOwnerRefList { - resourceName := topLevelObject.MetaDataName - provenanceNeeded := TotalClusterProvenance.checkIfProvenanceNeeded(resourceKind, resourceName) - if provenanceNeeded { - fmt.Println("###################################") - fmt.Printf("Building Provenance for %s %s\n", resourceKind, resourceName) - level := 0 - compositionTree := []CompositionTreeNode{} - buildProvenance(resourceKind, resourceName, level, &compositionTree) - TotalClusterProvenance.storeProvenance(topLevelObject, resourceKind, resourceName, &compositionTree) - fmt.Println("###################################\n") - provenanceToPrint = true - } - } - } - if provenanceToPrint { - TotalClusterProvenance.PrintProvenance() - } - time.Sleep(time.Second * 5) - } + // fmt.Println("Inside CollectProvenance") + // for { + readKindCompositionFile() + parse() + // time.Sleep(time.Second * 5) + // } } -func (cp *ClusterProvenance) checkIfProvenanceNeeded(resourceKind, resourceName string) bool { - cp.mux.Lock() - defer cp.mux.Unlock() - for _, provenanceItem := range cp.clusterProvenance { - kind := provenanceItem.Kind - name := provenanceItem.Name - if resourceKind == kind && resourceName == name { - return false - } +func readKindCompositionFile() { + // read from the opt file + filePath := os.Getenv("KIND_COMPOSITION_FILE") + yamlFile, err := ioutil.ReadFile(filePath) + if err != nil { + fmt.Printf("Error reading file:%s", err) + } + compositionsList := make([]composition, 0) + err = yaml.Unmarshal(yamlFile, &compositionsList) + for _, compositionObj := range compositionsList { + kind := compositionObj.Kind + endpoint := compositionObj.Endpoint + composition := compositionObj.Composition + plural := compositionObj.Plural + KindPluralMap[kind] = plural + kindVersionMap[kind] = endpoint + compositionMap[kind] = composition } - return true } -func readKindCompositionFile() { - // read from the opt file - filePath := os.Getenv("KIND_COMPOSITION_FILE") - yamlFile, err := ioutil.ReadFile(filePath) - if err != nil { - fmt.Printf("Error reading file:%s", err) - } - - compositionsList := make([]composition,0) - err = yaml.Unmarshal(yamlFile, &compositionsList) - - for _, compositionObj := range compositionsList { - kind := compositionObj.Kind - endpoint := compositionObj.Endpoint - composition := compositionObj.Composition - plural := compositionObj.Plural - //fmt.Printf("Kind:%s, Plural: %s Endpoint:%s, Composition:%s\n", kind, plural, endpoint, composition) - - KindPluralMap[kind] = plural - kindVersionMap[kind] = endpoint - compositionMap[kind] = composition - } - //printMaps() +func NewSpec() *Spec { + var s Spec + s.attributeToData = make(map[string]string) + return &s } -func printMaps() { - fmt.Println("Printing kindVersionMap") - for key, value := range kindVersionMap { - fmt.Printf("%s, %s\n", key, value) - } - fmt.Println("Printing KindPluralMap") - for key, value := range KindPluralMap { - fmt.Printf("%s, %s\n", key, value) - } - fmt.Println("Printing compositionMap") - for key, value := range compositionMap { - fmt.Printf("%s, %s\n", key, value) +func (s *Spec) String() string { + var b strings.Builder + for attribute, data := range s.attributeToData { + fmt.Fprintf(&b, "Attribute: %s Data: %s\n", attribute, data) } + return b.String() } -func getResourceKinds() []string { - resourceKindSlice := make([]string, 0) - for key, _ := range compositionMap { - resourceKindSlice = append(resourceKindSlice, key) +func (o Object) String() string { + var b strings.Builder + for version, spec := range o { + fmt.Fprintf(&b, "Version: %d Data: %s\n", version, spec.String()) } - return resourceKindSlice + return b.String() } -func getResourceNames(resourceKind string) []MetaDataAndOwnerReferences{ - resourceApiVersion := kindVersionMap[resourceKind] - resourceKindPlural := KindPluralMap[resourceKind] - content := getResourceListContent(resourceApiVersion, resourceKindPlural) - metaDataAndOwnerReferenceList := parseMetaData(content) - return metaDataAndOwnerReferenceList - /* - var resourceNameSlice []string - resourceNameSlice = make([]string, 0) - for _, metaDataRef := range metaDataAndOwnerReferenceList { - //fmt.Printf("%s\n", metaDataRef.MetaDataName) - resourceNameSlice = append(resourceNameSlice, metaDataRef.MetaDataName) - } - return resourceNameSlice - */ +func (o Object) LatestVersion(vNum int) int { + return len(o) } -func (cp *ClusterProvenance) PrintProvenance() { - cp.mux.Lock() - defer cp.mux.Unlock() - fmt.Println("Provenance of different Kinds in this Cluster") - for _, provenanceItem := range cp.clusterProvenance { - kind := provenanceItem.Kind - name := provenanceItem.Name - compositionTree := provenanceItem.CompositionTree - fmt.Printf("Kind: %s Name: %s Composition:\n", kind, name) - for _, compositionTreeNode := range *compositionTree { - level := compositionTreeNode.Level - childKind := compositionTreeNode.ChildKind - metaDataAndOwnerReferences := compositionTreeNode.Children - for _, metaDataNode := range metaDataAndOwnerReferences { - childName := metaDataNode.MetaDataName - childStatus := metaDataNode.Status - fmt.Printf(" %d %s %s %s\n", level, childKind, childName, childStatus) - } - } - fmt.Println("============================================") - } +func (o Object) Version(vNum int) Spec { + return o[vNum] +} + +//what happens if I delete the object? +//need to delete the ObjectFullProvenance for the object +//add type of ObjectFullProvenance, postgreses for example +func (o Object) SpecHistory() []string { + s := make([]string, len(o)) + for v, spec := range o { + s[v-1] = spec.String() + } + return s } -func getComposition(kind, name, status string, compositionTree *[]CompositionTreeNode) Composition { - var provenanceString string - fmt.Printf("Kind: %s Name: %s Composition:\n", kind, name) - provenanceString = "Kind: " + kind + " Name:" + name + " Composition:\n" - parentComposition := Composition{} - parentComposition.Level = 0 - parentComposition.Kind = kind - parentComposition.Name = name - parentComposition.Status = status - parentComposition.Children = []Composition{} - for _, compositionTreeNode := range *compositionTree { - level := compositionTreeNode.Level - childKind := compositionTreeNode.ChildKind - metaDataAndOwnerReferences := compositionTreeNode.Children - childComposition := Composition{} - for _, metaDataNode := range metaDataAndOwnerReferences { - childName := metaDataNode.MetaDataName - childStatus := metaDataNode.Status - fmt.Printf(" %d %s %s\n", level, childKind, childName) - provenanceString = provenanceString + " " + string(level) + " " + childKind + " " + childName + "\n" - childComposition.Level = level - childComposition.Kind = childKind - childComposition.Name = childName - childComposition.Status = childStatus +//add type of ObjectFullProvenance, postgreses for example +func (o Object) SpecHistoryInterval(vNumStart, vNumEnd int) []Spec { + s := make([]Spec, len(o)) + for v, spec := range o { + if v >= vNumStart && v <= vNumEnd { + s[v-1] = spec } - parentComposition.Children = append(parentComposition.Children, childComposition) } - return parentComposition + return s } -func (cp *ClusterProvenance) GetProvenance(resourceKind, resourceName string) string { - cp.mux.Lock() - defer cp.mux.Unlock() - var provenanceBytes []byte - var provenanceString string - compositions := []Composition{} - //fmt.Println("Provenance of different Kinds in this Cluster") - for _, provenanceItem := range cp.clusterProvenance { - kind := strings.ToLower(provenanceItem.Kind) - name := strings.ToLower(provenanceItem.Name) - status := provenanceItem.Status - compositionTree := provenanceItem.CompositionTree - resourceKind := strings.ToLower(resourceKind) - //TODO(devdattakulkarni): Make route registration and provenance keyed info - //to use same kind name (plural). Currently Provenance info is keyed on - //singular kind names. For now, trimming the 's' at the end - resourceKind = strings.TrimSuffix(resourceKind, "s") - resourceName := strings.ToLower(resourceName) - //fmt.Printf("Kind:%s, Kind:%s, Name:%s, Name:%s\n", kind, resourceKind, name, resourceName) - if resourceName == "*" { - if resourceKind == kind { - composition := getComposition(kind, name, status, compositionTree) - //provenanceInfo = provenanceInfo + provenanceForItem - compositions = append(compositions, composition) +//add type of ObjectFullProvenance, postgreses for example +func (o Object) FullDiff(vNumStart, vNumEnd int) string { + var b strings.Builder + sp1 := o[vNumStart] + sp2 := o[vNumEnd] + for attribute, data1 := range sp1.attributeToData { + if data2, ok := sp2.attributeToData[attribute]; ok { + if data1 != data2 { + fmt.Fprintf(&b, "FOUND DIFF") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1) + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2) + } else { + fmt.Fprintf(&b, "No difference for attribute %s \n", attribute) } - } else if resourceKind == kind && resourceName == name { - composition := getComposition(kind, name, status, compositionTree) - compositions = append(compositions, composition) + } else { //for the case where a key exists in spec 1 that doesn't exist in spec 2 + fmt.Fprintf(&b, "FOUND DIFF") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1) + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, "No attribute found.") } } - - fmt.Println("Compositions:\n%v",compositions) - provenanceBytes, err := json.Marshal(compositions) - if err != nil { - fmt.Println(err) + //for the case where a key exists in spec 2 that doesn't exist in spec 1 + for attribute, data1 := range sp2.attributeToData { + if _, ok := sp2.attributeToData[attribute]; !ok { + fmt.Fprintf(&b, "FOUND DIFF") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, "No attribute found.") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data1) + } } - fmt.Println("\nProvenance Bytes:%v", provenanceBytes) - provenanceString = string(provenanceBytes) - fmt.Println("\nProvenance String:%s", provenanceString) - return provenanceString + return b.String() } -// This stores Provenance information in memory. The provenance information will be lost -// when this Pod is deleted. -func (cp *ClusterProvenance) storeProvenance(topLevelObject MetaDataAndOwnerReferences, - resourceKind string, resourceName string, - compositionTree *[]CompositionTreeNode) { - cp.mux.Lock() - defer cp.mux.Unlock() - provenance := Provenance{ - Kind: resourceKind, - Name: resourceName, - Status: topLevelObject.Status, - CompositionTree: compositionTree, +//add type of ObjectFullProvenance, postgreses for example +func (o Object) FieldDiff(fieldName string, vNumStart, vNumEnd int) string { + var b strings.Builder + data1, ok1 := o[vNumStart].attributeToData[fieldName] + data2, ok2 := o[vNumEnd].attributeToData[fieldName] + switch { + case ok1 && ok2: + if data1 != data2 { + fmt.Fprintf(&b, "FOUND DIFF\n") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1) + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2) + } else { + fmt.Fprintf(&b, "No difference for attribute %s \n", fieldName) + } + case !ok1 && ok2: + fmt.Fprintf(&b, "FOUND DIFF") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, "No attribute found.") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, data2) + case ok1 && !ok2: + fmt.Fprintf(&b, "FOUND DIFF") + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumStart, data1) + fmt.Fprintf(&b, "Spec version %d:\n %s\n", vNumEnd, "No attribute found.") + case !ok1 && !ok2: + fmt.Fprintf(&b, "Attribute not found in either version %d or %d", vNumStart, vNumEnd) } - cp.clusterProvenance = append(cp.clusterProvenance, provenance) + return b.String() } -// This stores Provenance information in etcd accessible at the etcdServiceURL -// One option to deploy etcd is to use the CoreOS etcd-operator. -// The etcdServiceURL initialized in init() is for the example etcd cluster that -// will be created by the etcd-operator. See https://github.com/coreos/etcd-operator -//Ref:https://github.com/coreos/etcd/tree/master/client -func storeProvenance_etcd(resourceKind string, resourceName string, compositionTree *[]CompositionTreeNode) { - //fmt.Println("Entering storeProvenance") - jsonCompositionTree, err := json.Marshal(compositionTree) - if err != nil { - panic (err) - } - resourceProv := string(jsonCompositionTree) - cfg := client.Config{ - //Endpoints: []string{"http://192.168.99.100:32379"}, - Endpoints: []string{etcdServiceURL}, - Transport: client.DefaultTransport, - // set timeout per request to fail fast when the target endpoint is unavailable - //HeaderTimeoutPerRequest: time.Second, - } - //fmt.Printf("%v\n", cfg) - c, err := client.New(cfg) - if err != nil { - log.Fatal(err) - } - kapi := client.NewKeysAPI(c) - // set "/foo" key with "bar" value - //resourceKey := "/compositions/Deployment/pod42test-deployment" - //resourceProv := "{1 ReplicaSet; 2 Pod -1}" - resourceKey := string("/compositions/" + resourceKind + "/" + resourceName) - fmt.Printf("Setting %s->%s\n",resourceKey, resourceProv) - resp, err := kapi.Set(context.Background(), resourceKey, resourceProv, nil) - if err != nil { - log.Fatal(err) - } else { - // print common key info - log.Printf("Set is done. Metadata is %q\n", resp) +//Ref:https://www.sohamkamani.com/blog/2017/10/18/parsing-json-in-golang/#unstructured-data +func parse() { + + if _, err := os.Stat("kube-apiserver-audit.log"); os.IsNotExist(err) { + fmt.Println(fmt.Sprintf("could not stat the path %s", err)) + panic(err) } - fmt.Printf("Getting value for %s\n", resourceKey) - resp, err = kapi.Get(context.Background(), resourceKey, nil) + log, err := os.Open("/tmp/kube-apiserver-audit.log") if err != nil { - log.Fatal(err) - } else { - // print common key info - //log.Printf("Get is done. Metadata is %q\n", resp) - // print value - log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value) + fmt.Println(fmt.Sprintf("could not open the log file %s", err)) + panic(err) } - //fmt.Println("Exiting storeProvenance") -} + defer log.Close() -func buildProvenance(parentResourceKind string, parentResourceName string, level int, - compositionTree *[]CompositionTreeNode) { - //fmt.Printf("$$$$$ Building Provenance Level %d $$$$$ \n", level) - childResourceKindList, present := compositionMap[parentResourceKind] - if present { - level = level + 1 - for _, childResourceKind := range childResourceKindList { - childKindPlural := KindPluralMap[childResourceKind] - childResourceApiVersion := kindVersionMap[childResourceKind] - content := getResourceListContent(childResourceApiVersion, childKindPlural) - metaDataAndOwnerReferenceList := parseMetaData(content) - childrenList := filterChildren(&metaDataAndOwnerReferenceList, parentResourceName) - compTreeNode := CompositionTreeNode{ - Level: level, - ChildKind: childResourceKind, - Children: childrenList, - } - *compositionTree = append(*compositionTree, compTreeNode) + scanner := bufio.NewScanner(log) + for scanner.Scan() { - for _, metaDataRef := range childrenList { - resourceName := metaDataRef.MetaDataName - resourceKind := childResourceKind - buildProvenance(resourceKind, resourceName, level, compositionTree) - } + eventJson := scanner.Bytes() + + var event Event + err := json.Unmarshal(eventJson, &event) + if err != nil { + s := fmt.Sprintf("Problem parsing event's json %s", err) + fmt.Println(s) } - } else { - return - } -} -func getResourceListContent(resourceApiVersion, resourcePlural string) []byte { - //fmt.Println("Entering getResourceListContent") - url1 := fmt.Sprintf("https://%s:%s/%s/namespaces/%s/%s", serviceHost, servicePort, resourceApiVersion, Namespace, resourcePlural) - //fmt.Printf("Url:%s\n",url1) - caToken := getToken() - caCertPool := getCACert() - u, err := url.Parse(url1) - if err != nil { - panic(err) - } - req, err := http.NewRequest(httpMethod, u.String(), nil) - if err != nil { - fmt.Println(err) - } - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", string(caToken))) - client := &http.Client{ - Transport: &http.Transport{ - TLSClientConfig: &tls.Config{ - RootCAs: caCertPool, - }, - }, + requestobj := event.RequestObject + + ParseRequestObject(requestobj.Raw) } - resp, err := client.Do(req) - if err != nil { - log.Printf("sending request failed: %s", err.Error()) - fmt.Println(err) + if err := scanner.Err(); err != nil { + panic(err) } - defer resp.Body.Close() - resp_body, _ := ioutil.ReadAll(resp.Body) - - //fmt.Println(resp.Status) - //fmt.Println(string(resp_body)) - //fmt.Println("Exiting getResourceListContent") - return resp_body + fmt.Println("Done parsing.") } -//Ref:https://www.sohamkamani.com/blog/2017/10/18/parsing-json-in-golang/#unstructured-data -func parseMetaData(content []byte) []MetaDataAndOwnerReferences { - //fmt.Println("Entering parseMetaData") +func ParseRequestObject(requestObjBytes []byte) { + fmt.Println("entering parse request") + var result map[string]interface{} - json.Unmarshal([]byte(content), &result) - // We need to parse following from the result - // metadata.name - // metadata.ownerReferences.name - // metadata.ownerReferences.kind - // metadata.ownerReferences.apiVersion - //parentName := "podtest5-deployment" - metaDataSlice := []MetaDataAndOwnerReferences{} - items, ok := result["items"].([]interface{}) + json.Unmarshal([]byte(requestObjBytes), &result) + + l1, ok := result["metadata"].(map[string]interface{}) + if !ok { + sp, _ := result["spec"].(map[string]interface{}) + //TODO: for the case where a crd ObjectFullProvenance is first created, like initialize, + //the metadata spec is empty. instead the spec field has the data + fmt.Println(sp) + return + } + l2, ok := l1["annotations"].(map[string]interface{}) + l3, ok := l2["kubectl.kubernetes.io/last-applied-configuration"].(string) + if !ok { + fmt.Println("Incorrect parsing of the auditEvent.requestObj.metadata") + } + in := []byte(l3) + var raw map[string]interface{} + json.Unmarshal(in, &raw) + spec, ok := raw["spec"].(map[string]interface{}) if ok { - for _, item := range items { - //fmt.Println("=======================") - itemConverted := item.(map[string]interface{}) - var metadataProcessed, statusProcessed bool - metaDataRef := MetaDataAndOwnerReferences{} - for key, value := range itemConverted { - if key == "metadata" { - //fmt.Println("----") - //fmt.Println(key, value.(interface{})) - metadataMap := value.(map[string]interface{}) - for mkey, mvalue := range metadataMap { - //fmt.Printf("%v ==> %v\n", mkey, mvalue.(interface{})) - if mkey == "ownerReferences" { - ownerReferencesList := mvalue.([]interface{}) - for _, ownerReference := range ownerReferencesList { - ownerReferenceMap := ownerReference.(map[string]interface{}) - for okey, ovalue := range ownerReferenceMap { - //fmt.Printf("%v --> %v\n", okey, ovalue) - if okey == "name" { - metaDataRef.OwnerReferenceName = ovalue.(string) - } - if okey == "kind" { - metaDataRef.OwnerReferenceKind = ovalue.(string) - } - if okey == "apiVersion" { - metaDataRef.OwnerReferenceAPIVersion = ovalue.(string) - } - } - } - } - if mkey == "name" { - metaDataRef.MetaDataName = mvalue.(string) - } - } - metadataProcessed = true - } - if key == "status" { - statusMap := value.(map[string]interface{}) - var replicas, readyReplicas, availableReplicas float64 - for skey, svalue := range statusMap { - if skey == "phase" { - metaDataRef.Status = svalue.(string) - fmt.Println(metaDataRef.Status) - } - if skey == "replicas" { - replicas = svalue.(float64) - } - if skey == "readyReplicas" { - readyReplicas = svalue.(float64) - } - if skey == "availableReplicas" { - availableReplicas = svalue.(float64) - } - } - // Trying to be completely sure that we can set READY status - if replicas > 0 { - if replicas == availableReplicas && replicas == readyReplicas { - metaDataRef.Status = "Ready" - } - } - statusProcessed = true - } - if metadataProcessed && statusProcessed { - metaDataSlice = append(metaDataSlice, metaDataRef) - } - } - } + fmt.Println("Successfully parsed") } - //fmt.Println("Exiting parseMetaData") - return metaDataSlice -} -func filterChildren(metaDataSlice *[]MetaDataAndOwnerReferences, parentResourceName string) []MetaDataAndOwnerReferences { - metaDataSliceToReturn := []MetaDataAndOwnerReferences{} - for _, metaDataRef := range *metaDataSlice { - if metaDataRef.OwnerReferenceName == parentResourceName { - metaDataSliceToReturn = append(metaDataSliceToReturn, metaDataRef) + saveProvenance(spec) + + fmt.Println("exiting parse request") +} +func saveProvenance(spec map[string]interface{}) { + mySpec := *NewSpec() + newVersion := 1 + len(ObjectFullProvenance) + for attribute, value := range spec { + bytes, err := json.MarshalIndent(value, "", " ") + if err != nil { + fmt.Println("Error could not marshal json: " + err.Error()) } + attributeData := string(bytes) + mySpec.attributeToData[attribute] = attributeData } - return metaDataSliceToReturn + ObjectFullProvenance[newVersion] = mySpec } -// Ref:https://stackoverflow.com/questions/30690186/how-do-i-access-the-kubernetes-api-from-within-a-pod-container -func getToken() []byte { - caToken, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/token") - if err != nil { - panic(err) // cannot find token file +func printMaps() { + fmt.Println("Printing kindVersionMap") + for key, value := range kindVersionMap { + fmt.Printf("%s, %s\n", key, value) + } + fmt.Println("Printing KindPluralMap") + for key, value := range KindPluralMap { + fmt.Printf("%s, %s\n", key, value) + } + fmt.Println("Printing compositionMap") + for key, value := range compositionMap { + fmt.Printf("%s, %s\n", key, value) } - //fmt.Printf("Token:%s", caToken) - return caToken } -// Ref:https://stackoverflow.com/questions/30690186/how-do-i-access-the-kubernetes-api-from-within-a-pod-container -func getCACert() *cert.CertPool { - caCertPool := cert.NewCertPool() - caCert, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt") - if err != nil { - panic(err) // Can't find cert file +func getResourceKinds() []string { + resourceKindSlice := make([]string, 0) + for key, _ := range compositionMap { + resourceKindSlice = append(resourceKindSlice, key) } - //fmt.Printf("CaCert:%s",caCert) - caCertPool.AppendCertsFromPEM(caCert) - return caCertPool -} \ No newline at end of file + return resourceKindSlice +} diff --git a/pkg/provenance/types.go b/pkg/provenance/types.go index 20ed32a..40c4848 100644 --- a/pkg/provenance/types.go +++ b/pkg/provenance/types.go @@ -1,61 +1,18 @@ package provenance -import ( - "sync" -) - // Used for unmarshalling JSON output from the main API server type composition struct { - Kind string `yaml:"kind"` - Plural string `yaml:"plural"` - Endpoint string `yaml:"endpoint"` + Kind string `yaml:"kind"` + Plural string `yaml:"plural"` + Endpoint string `yaml:"endpoint"` Composition []string `yaml:"composition"` } // Used for Final output type Composition struct { - Level int - Kind string - Name string - Status string + Level int + Kind string + Name string + Status string Children []Composition } - -// Used to store information queried from the main API server -type MetaDataAndOwnerReferences struct { - MetaDataName string - Status string - OwnerReferenceName string - OwnerReferenceKind string - OwnerReferenceAPIVersion string -} - -// Used for intermediate storage -- probably can be combined/merged with -// type Provenance and/or type Composition -type CompositionTreeNode struct { - Level int - ChildKind string - Children []MetaDataAndOwnerReferences -} - -// Used for intermediate storage -- probably can be merged with Composition -type Provenance struct { - Kind string - Name string - Status string - CompositionTree *[]CompositionTreeNode -} - -// Used to hold entire composition Provenance of all the Kinds -type ClusterProvenance struct { - clusterProvenance []Provenance - mux sync.Mutex -} - -var ( - TotalClusterProvenance ClusterProvenance -) - -func init() { - TotalClusterProvenance = ClusterProvenance{} -} \ No newline at end of file