diff --git a/cmd/cli/kubectl-kyverno/main.go b/cmd/cli/kubectl-kyverno/main.go new file mode 100644 index 0000000000..a5adfa5a9e --- /dev/null +++ b/cmd/cli/kubectl-kyverno/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/nirmata/kyverno/pkg/kyverno" +) + +func main() { + kyverno.CLI() +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go deleted file mode 100644 index 33bc524e9c..0000000000 --- a/cmd/cli/main.go +++ /dev/null @@ -1,25 +0,0 @@ -package main - -import ( - goflag "flag" - "fmt" - "os" - - "github.com/nirmata/kyverno/pkg/config" - kyverno "github.com/nirmata/kyverno/pkg/kyverno" - flag "github.com/spf13/pflag" -) - -func main() { - cmd := kyverno.NewDefaultKyvernoCommand() - if err := cmd.Execute(); err != nil { - fmt.Fprintf(os.Stderr, "%v\n", err) - os.Exit(1) - } -} - -func init() { - flag.CommandLine.AddGoFlagSet(goflag.CommandLine) - config.LogDefaultFlags() - flag.Parse() -} diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go index eaf642e1e0..9c5a9141ba 100644 --- a/cmd/initContainer/main.go +++ b/cmd/initContainer/main.go @@ -6,6 +6,8 @@ package main import ( "flag" "os" + "regexp" + "strconv" "sync" "time" @@ -44,6 +46,11 @@ func main() { glog.Fatalf("Error creating client: %v\n", err) } + // Exit for unsupported version of kubernetes cluster + // https://github.com/nirmata/kyverno/issues/700 + // - supported from v1.12.7+ + isVersionSupported(client) + requests := []request{ // Resource {validatingWebhookConfigKind, config.ValidatingWebhookConfigurationName}, @@ -206,3 +213,32 @@ func merge(done <-chan struct{}, stopCh <-chan struct{}, processes ...<-chan err }() return out } + +func isVersionSupported(client *client.Client) { + serverVersion, err := client.DiscoveryClient.GetServerVersion() + if err != nil { + glog.Fatalf("Failed to get kubernetes server version: %v\n", err) + } + exp := regexp.MustCompile(`v(\d*).(\d*).(\d*)`) + groups := exp.FindAllStringSubmatch(serverVersion.String(), -1) + if len(groups) != 1 || len(groups[0]) != 4 { + glog.Fatalf("Failed to extract kubernetes server version: %v.err %v\n", serverVersion, err) + } + // convert string to int + // assuming the version are always intergers + major, err := strconv.Atoi(groups[0][1]) + if err != nil { + glog.Fatalf("Failed to extract kubernetes major server version: %v.err %v\n", serverVersion, err) + } + minor, err := strconv.Atoi(groups[0][2]) + if err != nil { + glog.Fatalf("Failed to extract kubernetes minor server version: %v.err %v\n", serverVersion, err) + } + sub, err := strconv.Atoi(groups[0][3]) + if err != nil { + glog.Fatalf("Failed to extract kubernetes sub minor server version:%v. err %v\n", serverVersion, err) + } + if major <= 1 && minor <= 12 && sub < 7 { + glog.Fatalf("Unsupported kubernetes server version %s. Kyverno is supported from version v1.12.7+", serverVersion) + } +} diff --git a/go.mod b/go.mod index 618375f388..593b08fdef 100644 --- a/go.mod +++ b/go.mod @@ -9,14 +9,16 @@ require ( github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 // indirect github.com/googleapis/gnostic v0.3.1 + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/golang-lru v0.5.3 // indirect github.com/imdario/mergo v0.3.8 // indirect github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af github.com/json-iterator/go v1.1.9 // indirect github.com/minio/minio v0.0.0-20200114012931-30922148fbb5 github.com/ory/go-acc v0.1.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/spf13/cobra v0.0.5 - github.com/spf13/pflag v1.0.5 + github.com/spf13/pflag v1.0.5 // indirect github.com/tevino/abool v0.0.0-20170917061928-9b9efcf221b5 golang.org/x/crypto v0.0.0-20200109152110-61a87790db17 // indirect golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect @@ -29,10 +31,12 @@ require ( gotest.tools v2.2.0+incompatible k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d + k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible k8s.io/klog v1.0.0 // indirect k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 // indirect + sigs.k8s.io/kustomize v2.0.3+incompatible // indirect ) // Added for go1.13 migration https://github.com/golang/go/issues/32805 diff --git a/go.sum b/go.sum index de7e2f3a59..9b6fc04130 100644 --- a/go.sum +++ b/go.sum @@ -24,7 +24,9 @@ github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcy github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.0.0 h1:0GoNN3taZV6QI81IXgCbxMyEaJDXMSIjArYBCYzVVvs= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2 h1:JCHLVE3B+kJde7bIEo5N4J+ZbLhp0J1Fs+ulyRws4gE= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.24.1/go.mod h1:fGP8eQ6PugKEI0iUETYYtnP6d1pH/bdDMTel1X5ajsU= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= @@ -95,6 +97,7 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP github.com/eclipse/paho.mqtt.golang v1.2.0/go.mod h1:H9keYFcgq3Qr5OUJm/JZI/i6U7joQ8SYLhZwfeOo6Ts= github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/elazarl/goproxy v0.0.0-20181003060214-f58a169a71a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -108,6 +111,7 @@ github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -116,9 +120,13 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1 h1:wSt/4CYxs70xbATrGXhokKF1i0tZjENLOo1ioIO13zk= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9 h1:tF+augKRWlWx0J0B7ZyyKSiTyV6E1zZe+7b3qQlcEf8= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501 h1:C1JKChikHGpXwT5UQDFaryIpDtyyGL/CR6C2kB7F1oc= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87 h1:zP3nY8Tk2E6RTkqGYrarZXuzh+ffyLDljLxCy1iJw80= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -316,7 +324,9 @@ github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -351,6 +361,8 @@ github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -443,6 +455,7 @@ github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czP github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5 h1:0x4qcEHDpruK6ML/m/YSlFUUu0UpRD3I2PHsNCuGnyA= github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= @@ -566,6 +579,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pierrec/lz4 v2.2.6+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -950,6 +965,8 @@ k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbu k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= +k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a h1:REMzGxu+NpG9dPRsE9my/fw9iYIecz1S8UFFl6hbe18= +k8s.io/cli-runtime v0.0.0-20191004110135-b9eb767d2e1a/go.mod h1:qWnH3/b8sp/l7EvlDh7ulDU3UWA4P4N1NFbEEP791tM= k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible h1:bK03DJulJi9j05gwnXUufcs2j7h4M85YFvJ0dIlQ9k4= k8s.io/client-go v11.0.1-0.20190516230509-ae8359b20417+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= @@ -962,6 +979,8 @@ k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKf k8s.io/utils v0.0.0-20200109141947-94aeca20bf09 h1:sz6xjn8QP74104YNmJpzLbJ+a3ZtHt0tkD0g8vpdWNw= k8s.io/utils v0.0.0-20200109141947-94aeca20bf09/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= +sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/config/config.go b/pkg/config/config.go index 3ef21fbfee..241728a2c7 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -97,6 +97,6 @@ func CreateClientConfig(kubeconfig string) (*rest.Config, error) { glog.Info("Using in-cluster configuration") return rest.InClusterConfig() } - glog.Infof("Using configuration from '%s'", kubeconfig) + glog.V(4).Infof("Using configuration from '%s'", kubeconfig) return clientcmd.BuildConfigFromFlags("", kubeconfig) } diff --git a/pkg/dclient/client.go b/pkg/dclient/client.go index b2834949e5..9bb212369e 100644 --- a/pkg/dclient/client.go +++ b/pkg/dclient/client.go @@ -16,6 +16,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" patchTypes "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/discovery" "k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/dynamic" @@ -213,6 +214,7 @@ func convertToCSR(obj *unstructured.Unstructured) (*certificates.CertificateSign //IDiscovery provides interface to mange Kind and GVR mapping type IDiscovery interface { GetGVRFromKind(kind string) schema.GroupVersionResource + GetServerVersion() (*version.Info, error) } // SetDiscovery sets the discovery client implementation @@ -265,6 +267,11 @@ func (c ServerPreferredResources) GetGVRFromKind(kind string) schema.GroupVersio return gvr } +//GetServerVersion returns the server version of the cluster +func (c ServerPreferredResources) GetServerVersion() (*version.Info, error) { + return c.cachedClient.ServerVersion() +} + func loadServerResources(k string, cdi discovery.CachedDiscoveryInterface) (schema.GroupVersionResource, error) { serverresources, err := cdi.ServerPreferredResources() emptyGVR := schema.GroupVersionResource{} diff --git a/pkg/dclient/utils.go b/pkg/dclient/utils.go index 401e900efa..a8a0275b95 100644 --- a/pkg/dclient/utils.go +++ b/pkg/dclient/utils.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/version" "k8s.io/client-go/dynamic/fake" kubernetesfake "k8s.io/client-go/kubernetes/fake" ) @@ -64,6 +65,10 @@ func (c *fakeDiscoveryClient) getGVR(resource string) schema.GroupVersionResourc return schema.GroupVersionResource{} } +func (c *fakeDiscoveryClient) GetServerVersion() (*version.Info, error) { + return nil, nil +} + func (c *fakeDiscoveryClient) GetGVRFromKind(kind string) schema.GroupVersionResource { resource := strings.ToLower(kind) + "s" return c.getGVR(resource) diff --git a/pkg/engine/mutation.go b/pkg/engine/mutation.go index 8fdf1cc33b..6ac8dae13d 100644 --- a/pkg/engine/mutation.go +++ b/pkg/engine/mutation.go @@ -88,7 +88,8 @@ func Mutate(policyContext PolicyContext) (resp response.EngineResponse) { glog.V(4).Infof(ruleResponse.Message) continue } - glog.Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName()) + + glog.V(4).Infof("Mutate overlay in rule '%s' successfully applied on %s/%s/%s", rule.Name, resource.GetKind(), resource.GetNamespace(), resource.GetName()) } resp.PolicyResponse.Rules = append(resp.PolicyResponse.Rules, ruleResponse) diff --git a/pkg/kyverno/apply/apply.go b/pkg/kyverno/apply/apply.go deleted file mode 100644 index 0cfad10b10..0000000000 --- a/pkg/kyverno/apply/apply.go +++ /dev/null @@ -1,251 +0,0 @@ -package apply - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "os" - - "github.com/golang/glog" - kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1" - "github.com/nirmata/kyverno/pkg/engine" - "github.com/spf13/cobra" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - schema "k8s.io/apimachinery/pkg/runtime/schema" - yaml "k8s.io/apimachinery/pkg/util/yaml" - memory "k8s.io/client-go/discovery/cached/memory" - dynamic "k8s.io/client-go/dynamic" - kubernetes "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/restmapper" -) - -const ( - applyExample = ` # Apply a policy to the resource. - kyverno apply @policy.yaml @resource.yaml - kyverno apply @policy.yaml @resourceDir/ - kyverno apply @policy.yaml @resource.yaml --kubeconfig=$PATH_TO_KUBECONFIG_FILE` - - defaultYamlSeparator = "---" -) - -// NewCmdApply returns the apply command for kyverno -func NewCmdApply(in io.Reader, out, errout io.Writer) *cobra.Command { - var kubeconfig string - cmd := &cobra.Command{ - Use: "apply", - Short: "Apply policy on the resource(s)", - Example: applyExample, - Run: func(cmd *cobra.Command, args []string) { - policy, resources := complete(kubeconfig, args) - output := applyPolicy(policy, resources) - fmt.Printf("%v\n", output) - }, - } - - cmd.Flags().StringVar(&kubeconfig, "kubeconfig", "", "path to kubeconfig file") - return cmd -} - -func complete(kubeconfig string, args []string) (*kyverno.ClusterPolicy, []*resourceInfo) { - policyDir, resourceDir, err := validateDir(args) - if err != nil { - glog.Errorf("Failed to parse file path, err: %v\n", err) - os.Exit(1) - } - - // extract policy - policy, err := extractPolicy(policyDir) - if err != nil { - glog.Errorf("Failed to extract policy: %v\n", err) - os.Exit(1) - } - - // extract rawResource - resources, err := extractResource(resourceDir, kubeconfig) - if err != nil { - glog.Errorf("Failed to parse resource: %v", err) - os.Exit(1) - } - - return policy, resources -} - -func applyPolicy(policy *kyverno.ClusterPolicy, resources []*resourceInfo) (output string) { - for _, resource := range resources { - patchedDocument, err := applyPolicyOnRaw(policy, resource.rawResource, resource.gvk) - if err != nil { - glog.Errorf("Error applying policy on resource %s, err: %v\n", resource.gvk.Kind, err) - continue - } - - out, err := prettyPrint(patchedDocument) - if err != nil { - glog.Errorf("JSON parse error: %v\n", err) - continue - } - - output = output + fmt.Sprintf("---\n%s", string(out)) - } - return -} - -func applyPolicyOnRaw(policy *kyverno.ClusterPolicy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) { - patchedResource := rawResource - var err error - - rname := engine.ParseNameFromObject(rawResource) - rns := engine.ParseNamespaceFromObject(rawResource) - resource, err := ConvertToUnstructured(rawResource) - if err != nil { - return nil, err - } - //TODO check if the kind information is present resource - // Process Mutation - engineResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) - if !engineResponse.IsSuccesful() { - glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) - for _, r := range engineResponse.PolicyResponse.Rules { - glog.Warning(r.Message) - } - } else if len(engineResponse.PolicyResponse.Rules) > 0 { - glog.Infof("Mutation from policy %s has applied successfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns) - - // Process Validation - engineResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) - - if !engineResponse.IsSuccesful() { - glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns) - for _, r := range engineResponse.PolicyResponse.Rules { - glog.Warning(r.Message) - } - return patchedResource, fmt.Errorf("policy %s on resource %s/%s not satisfied", policy.Name, rname, rns) - } else if len(engineResponse.PolicyResponse.Rules) > 0 { - glog.Infof("Validation from policy %s has applied successfully to %s %s/%s", policy.Name, gvk.Kind, rname, rns) - } - } - return patchedResource, nil -} - -func extractPolicy(fileDir string) (*kyverno.ClusterPolicy, error) { - policy := &kyverno.ClusterPolicy{} - - file, err := loadFile(fileDir) - if err != nil { - return nil, fmt.Errorf("failed to load file: %v", err) - } - - policyBytes, err := yaml.ToJSON(file) - if err != nil { - return nil, err - } - - if err := json.Unmarshal(policyBytes, policy); err != nil { - return nil, fmt.Errorf("failed to decode policy %s, err: %v", policy.Name, err) - } - - if policy.TypeMeta.Kind != "ClusterPolicy" { - return nil, fmt.Errorf("failed to parse policy") - } - - return policy, nil -} - -type resourceInfo struct { - rawResource []byte - gvk *metav1.GroupVersionKind -} - -func extractResource(fileDir, kubeconfig string) ([]*resourceInfo, error) { - var files []string - var resources []*resourceInfo - - // check if applied on multiple resources - isDir, err := isDir(fileDir) - if err != nil { - return nil, err - } - - if isDir { - files, err = scanDir(fileDir) - if err != nil { - return nil, err - } - } else { - files = []string{fileDir} - } - - for _, dir := range files { - data, err := loadFile(dir) - if err != nil { - glog.Warningf("Error while loading file: %v\n", err) - continue - } - - dd := bytes.Split(data, []byte(defaultYamlSeparator)) - - for _, d := range dd { - decode := scheme.Codecs.UniversalDeserializer().Decode - obj, gvk, err := decode([]byte(d), nil, nil) - if err != nil { - glog.Warningf("Error while decoding YAML object, err: %s\n", err) - continue - } - - actualObj, err := convertToActualObject(kubeconfig, gvk, obj) - if err != nil { - glog.V(3).Infof("Failed to convert resource %s to actual k8s object: %v\n", gvk.Kind, err) - glog.V(3).Infof("Apply policy on raw resource.\n") - } - - raw, err := json.Marshal(actualObj) - if err != nil { - glog.Warningf("Error while marshalling manifest, err: %v\n", err) - continue - } - - gvkInfo := &metav1.GroupVersionKind{Group: gvk.Group, Version: gvk.Version, Kind: gvk.Kind} - resources = append(resources, &resourceInfo{rawResource: raw, gvk: gvkInfo}) - } - } - - return resources, err -} - -func convertToActualObject(kubeconfig string, gvk *schema.GroupVersionKind, obj runtime.Object) (interface{}, error) { - clientConfig, err := createClientConfig(kubeconfig) - if err != nil { - return obj, err - } - - dynamicClient, err := dynamic.NewForConfig(clientConfig) - if err != nil { - return obj, err - } - - kclient, err := kubernetes.NewForConfig(clientConfig) - if err != nil { - return obj, err - } - - asUnstructured := &unstructured.Unstructured{} - if err := scheme.Scheme.Convert(obj, asUnstructured, nil); err != nil { - return obj, err - } - - mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(kclient.Discovery())) - mapping, err := mapper.RESTMapping(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind}, gvk.Version) - if err != nil { - return obj, err - } - - actualObj, err := dynamicClient.Resource(mapping.Resource).Namespace("default").Create(asUnstructured, metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}}) - if err != nil { - return obj, err - } - - return actualObj, nil -} diff --git a/pkg/kyverno/apply/command.go b/pkg/kyverno/apply/command.go new file mode 100644 index 0000000000..15132caa50 --- /dev/null +++ b/pkg/kyverno/apply/command.go @@ -0,0 +1,375 @@ +package apply + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/nirmata/kyverno/pkg/kyverno/sanitizedError" + + policy2 "github.com/nirmata/kyverno/pkg/policy" + + "github.com/golang/glog" + + "k8s.io/apimachinery/pkg/runtime/schema" + + "k8s.io/client-go/discovery" + + "k8s.io/apimachinery/pkg/util/yaml" + + "github.com/nirmata/kyverno/pkg/engine" + + engineutils "github.com/nirmata/kyverno/pkg/engine/utils" + + "k8s.io/apimachinery/pkg/runtime" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/spf13/cobra" + yamlv2 "gopkg.in/yaml.v2" + "k8s.io/cli-runtime/pkg/genericclioptions" + "k8s.io/client-go/kubernetes/scheme" +) + +func Command() *cobra.Command { + var cmd *cobra.Command + var resourcePaths []string + var cluster bool + + kubernetesConfig := genericclioptions.NewConfigFlags(true) + + cmd = &cobra.Command{ + Use: "apply", + Short: "Applies policies on resources", + Example: fmt.Sprintf("To apply on a resource:\nkyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --resource=/path/to/resource1 --resource=/path/to/resource2\n\nTo apply on a cluster\nkyverno apply /path/to/policy.yaml /path/to/folderOfPolicies --cluster"), + RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { + defer func() { + if err != nil { + if !sanitizedError.IsErrorSanitized(err) { + glog.V(4).Info(err) + err = fmt.Errorf("Internal error") + } + } + }() + + if len(resourcePaths) == 0 && !cluster { + return sanitizedError.New(fmt.Sprintf("Specify path to resource file or cluster name")) + } + + policies, err := getPolicies(policyPaths) + if err != nil { + if !sanitizedError.IsErrorSanitized(err) { + return sanitizedError.New("Could not parse policy paths") + } else { + return err + } + } + + for _, policy := range policies { + err := policy2.Validate(*policy) + if err != nil { + return sanitizedError.New(fmt.Sprintf("Policy %v is not valid", policy.Name)) + } + } + + var dClient discovery.CachedDiscoveryInterface + if cluster { + dClient, err = kubernetesConfig.ToDiscoveryClient() + if err != nil { + return sanitizedError.New(fmt.Errorf("Issues with kubernetes Config").Error()) + } + } + + resources, err := getResources(policies, resourcePaths, dClient) + if err != nil { + return sanitizedError.New(fmt.Errorf("Issues fetching resources").Error()) + } + + for i, policy := range policies { + for j, resource := range resources { + if !(j == 0 && i == 0) { + fmt.Printf("\n\n=======================================================================\n") + } + + err = applyPolicyOnResource(policy, resource) + if err != nil { + return sanitizedError.New(fmt.Errorf("Issues applying policy %v on resource %v", policy.Name, resource.GetName()).Error()) + } + } + } + + return nil + }, + } + + cmd.Flags().StringArrayVarP(&resourcePaths, "resource", "r", []string{}, "Path to resource files") + cmd.Flags().BoolVarP(&cluster, "cluster", "c", false, "Checks if policies should be applied to cluster in the current context") + + return cmd +} + +func getResources(policies []*v1.ClusterPolicy, resourcePaths []string, dClient discovery.CachedDiscoveryInterface) ([]*unstructured.Unstructured, error) { + var resources []*unstructured.Unstructured + var err error + + if dClient != nil { + var resourceTypesMap = make(map[string]bool) + var resourceTypes []string + for _, policy := range policies { + for _, rule := range policy.Spec.Rules { + for _, kind := range rule.MatchResources.Kinds { + resourceTypesMap[kind] = true + } + } + } + + for kind := range resourceTypesMap { + resourceTypes = append(resourceTypes, kind) + } + + resources, err = getResourcesOfTypeFromCluster(resourceTypes, dClient) + if err != nil { + return nil, err + } + } + + for _, resourcePath := range resourcePaths { + resource, err := getResource(resourcePath) + if err != nil { + return nil, err + } + + resources = append(resources, resource) + } + + return resources, nil +} + +func getResourcesOfTypeFromCluster(resourceTypes []string, dClient discovery.CachedDiscoveryInterface) ([]*unstructured.Unstructured, error) { + var resources []*unstructured.Unstructured + + for _, kind := range resourceTypes { + endpoint, err := getListEndpointForKind(kind) + if err != nil { + return nil, err + } + + listObjectRaw, err := dClient.RESTClient().Get().RequestURI(endpoint).Do().Raw() + if err != nil { + return nil, err + } + + listObject, err := engineutils.ConvertToUnstructured(listObjectRaw) + if err != nil { + return nil, err + } + + resourceList, err := listObject.ToList() + if err != nil { + return nil, err + } + + version := resourceList.GetAPIVersion() + for _, resource := range resourceList.Items { + resource.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "", + Version: version, + Kind: kind, + }) + resources = append(resources, resource.DeepCopy()) + } + } + + return resources, nil +} + +func getPoliciesInDir(path string) ([]*v1.ClusterPolicy, error) { + var policies []*v1.ClusterPolicy + + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + + for _, file := range files { + if file.IsDir() { + policiesFromDir, err := getPoliciesInDir(filepath.Join(path, file.Name())) + if err != nil { + return nil, err + } + + policies = append(policies, policiesFromDir...) + } else { + policy, err := getPolicy(filepath.Join(path, file.Name())) + if err != nil { + return nil, err + } + + policies = append(policies, policy) + } + } + + return policies, nil +} + +func getPolicies(paths []string) ([]*v1.ClusterPolicy, error) { + var policies = make([]*v1.ClusterPolicy, 0, len(paths)) + for _, path := range paths { + path = filepath.Clean(path) + + fileDesc, err := os.Stat(path) + if err != nil { + return nil, err + } + + if fileDesc.IsDir() { + policiesFromDir, err := getPoliciesInDir(path) + if err != nil { + return nil, err + } + + policies = append(policies, policiesFromDir...) + } else { + policy, err := getPolicy(path) + if err != nil { + return nil, err + } + + policies = append(policies, policy) + } + } + + return policies, nil +} + +func getPolicy(path string) (*v1.ClusterPolicy, error) { + policy := &v1.ClusterPolicy{} + + file, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to load file: %v", err) + } + + policyBytes, err := yaml.ToJSON(file) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(policyBytes, policy); err != nil { + return nil, sanitizedError.New(fmt.Sprintf("failed to decode policy in %s", path)) + } + + if policy.TypeMeta.Kind != "ClusterPolicy" { + return nil, sanitizedError.New(fmt.Sprintf("resource %v is not a cluster policy", policy.Name)) + } + + return policy, nil +} + +func getResource(path string) (*unstructured.Unstructured, error) { + + resourceYaml, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + + decode := scheme.Codecs.UniversalDeserializer().Decode + resourceObject, metaData, err := decode(resourceYaml, nil, nil) + if err != nil { + return nil, err + } + + resourceUnstructured, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&resourceObject) + if err != nil { + return nil, err + } + + resourceJSON, err := json.Marshal(resourceUnstructured) + if err != nil { + return nil, err + } + + resource, err := engineutils.ConvertToUnstructured(resourceJSON) + if err != nil { + return nil, err + } + + resource.SetGroupVersionKind(*metaData) + + if resource.GetNamespace() == "" { + resource.SetNamespace("default") + } + + return resource, nil +} + +func applyPolicyOnResource(policy *v1.ClusterPolicy, resource *unstructured.Unstructured) error { + + fmt.Printf("\n\nApplying Policy %s on Resource %s/%s/%s", policy.Name, resource.GetNamespace(), resource.GetKind(), resource.GetName()) + + mutateResponse := engine.Mutate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) + if !mutateResponse.IsSuccesful() { + fmt.Printf("\n\nMutation:") + fmt.Printf("\nFailed to apply mutation") + for i, r := range mutateResponse.PolicyResponse.Rules { + fmt.Printf("\n%d. %s", i+1, r.Message) + } + fmt.Printf("\n\n") + } else { + if len(mutateResponse.PolicyResponse.Rules) > 0 { + fmt.Printf("\n\nMutation:") + fmt.Printf("\nMutation has been applied succesfully") + yamlEncodedResource, err := yamlv2.Marshal(mutateResponse.PatchedResource.Object) + if err != nil { + return err + } + + fmt.Printf("\n\n" + string(yamlEncodedResource)) + fmt.Printf("\n\n") + } + } + + validateResponse := engine.Validate(engine.PolicyContext{Policy: *policy, NewResource: mutateResponse.PatchedResource}) + if !validateResponse.IsSuccesful() { + fmt.Printf("\n\nValidation:") + fmt.Printf("\nResource is invalid") + for i, r := range validateResponse.PolicyResponse.Rules { + fmt.Printf("\n%d. %s", i+1, r.Message) + } + fmt.Printf("\n\n") + } else { + if len(validateResponse.PolicyResponse.Rules) > 0 { + fmt.Printf("\n\nValidation:") + fmt.Printf("\nResource is valid") + fmt.Printf("\n\n") + } + } + + var policyHasGenerate bool + for _, rule := range policy.Spec.Rules { + if rule.HasGenerate() { + policyHasGenerate = true + } + } + + if policyHasGenerate { + generateResponse := engine.Generate(engine.PolicyContext{Policy: *policy, NewResource: *resource}) + if len(generateResponse.PolicyResponse.Rules) > 0 { + fmt.Printf("\n\nGenerate:") + fmt.Printf("\nResource is valid") + fmt.Printf("\n\n") + } else { + fmt.Printf("\n\nGenerate:") + fmt.Printf("\nResource is invalid") + for i, r := range generateResponse.PolicyResponse.Rules { + fmt.Printf("\n%d. %s", i+1, r.Message) + } + fmt.Printf("\n\n") + } + } + + return nil +} diff --git a/pkg/kyverno/apply/helper.go b/pkg/kyverno/apply/helper.go new file mode 100644 index 0000000000..04dc142a9a --- /dev/null +++ b/pkg/kyverno/apply/helper.go @@ -0,0 +1,37 @@ +package apply + +import ( + "fmt" + "strings" + + "github.com/nirmata/kyverno/pkg/openapi" +) + +func getListEndpointForKind(kind string) (string, error) { + + definitionName := openapi.GetDefinitionNameFromKind(kind) + definitionNameWithoutPrefix := strings.Replace(definitionName, "io.k8s.", "", -1) + + parts := strings.Split(definitionNameWithoutPrefix, ".") + definitionPrefix := strings.Join(parts[:len(parts)-1], ".") + + defPrefixToApiPrefix := map[string]string{ + "api.core.v1": "/api/v1", + "api.apps.v1": "/apis/apps/v1", + "api.batch.v1": "/apis/batch/v1", + "api.admissionregistration.v1": "/apis/admissionregistration.k8s.io/v1", + "kube-aggregator.pkg.apis.apiregistration.v1": "/apis/apiregistration.k8s.io/v1", + "apiextensions-apiserver.pkg.apis.apiextensions.v1": "/apis/apiextensions.k8s.io/v1", + "api.autoscaling.v1": "/apis/autoscaling/v1/", + "api.storage.v1": "/apis/storage.k8s.io/v1", + "api.coordination.v1": "/apis/coordination.k8s.io/v1", + "api.scheduling.v1": "/apis/scheduling.k8s.io/v1", + "api.rbac.v1": "/apis/rbac.authorization.k8s.io/v1", + } + + if defPrefixToApiPrefix[definitionPrefix] == "" { + return "", fmt.Errorf("Unsupported resource type %v", kind) + } + + return defPrefixToApiPrefix[definitionPrefix] + "/" + strings.ToLower(kind) + "s", nil +} diff --git a/pkg/kyverno/apply/util.go b/pkg/kyverno/apply/util.go deleted file mode 100644 index 6d5ac0170c..0000000000 --- a/pkg/kyverno/apply/util.go +++ /dev/null @@ -1,107 +0,0 @@ -package apply - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strings" - - "github.com/golang/glog" - yamlv2 "gopkg.in/yaml.v2" - unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - rest "k8s.io/client-go/rest" - clientcmd "k8s.io/client-go/tools/clientcmd" -) - -func createClientConfig(kubeconfig string) (*rest.Config, error) { - if kubeconfig == "" { - defaultKC := defaultKubeconfigPath() - if _, err := os.Stat(defaultKC); err == nil { - kubeconfig = defaultKC - } - } - - return clientcmd.BuildConfigFromFlags("", kubeconfig) -} - -func defaultKubeconfigPath() string { - home, err := os.UserHomeDir() - if err != nil { - glog.Warningf("Warning: failed to get home dir: %v\n", err) - return "" - } - - return filepath.Join(home, ".kube", "config") -} - -func loadFile(fileDir string) ([]byte, error) { - if _, err := os.Stat(fileDir); os.IsNotExist(err) { - return nil, err - } - - return ioutil.ReadFile(fileDir) -} - -func validateDir(args []string) (policyDir, resourceDir string, err error) { - if len(args) != 2 { - return "", "", fmt.Errorf("missing policy and/or resource manifest") - } - - if strings.HasPrefix(args[0], "@") { - policyDir = args[0][1:] - } - - if strings.HasPrefix(args[1], "@") { - resourceDir = args[1][1:] - } - return -} - -func prettyPrint(data []byte) ([]byte, error) { - out := make(map[interface{}]interface{}) - if err := yamlv2.Unmarshal(data, &out); err != nil { - return nil, err - } - - return yamlv2.Marshal(&out) -} - -func isDir(dir string) (bool, error) { - fi, err := os.Stat(dir) - if err != nil { - return false, err - } - - return fi.IsDir(), nil -} - -func scanDir(dir string) ([]string, error) { - var res []string - - err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v", dir, err) - } - - res = append(res, path) - return nil - }) - - if err != nil { - return nil, fmt.Errorf("error walking the path %q: %v", dir, err) - } - - return res[1:], nil -} - -//ConvertToUnstructured converts the resource to unstructured format -func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) { - resource := &unstructured.Unstructured{} - err := resource.UnmarshalJSON(data) - if err != nil { - glog.V(4).Infof("failed to unmarshall resource: %v", err) - return nil, err - } - return resource, nil -} diff --git a/pkg/kyverno/cmd.go b/pkg/kyverno/cmd.go deleted file mode 100644 index ac31e2a24f..0000000000 --- a/pkg/kyverno/cmd.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "io" - "os" - - "github.com/nirmata/kyverno/pkg/kyverno/apply" - "github.com/nirmata/kyverno/pkg/kyverno/version" - "github.com/spf13/cobra" -) - -// NewDefaultKyvernoCommand ... -func NewDefaultKyvernoCommand() *cobra.Command { - return NewKyvernoCommand(os.Stdin, os.Stdout, os.Stderr) -} - -// NewKyvernoCommand returns the new kynerno command -func NewKyvernoCommand(in io.Reader, out, errout io.Writer) *cobra.Command { - cmds := &cobra.Command{ - Use: "kyverno", - Short: "kyverno manages native policies of Kubernetes", - } - - cmds.AddCommand(apply.NewCmdApply(in, out, errout)) - cmds.AddCommand(version.NewCmdVersion(out)) - return cmds -} diff --git a/pkg/kyverno/main.go b/pkg/kyverno/main.go new file mode 100644 index 0000000000..d0d1163ef6 --- /dev/null +++ b/pkg/kyverno/main.go @@ -0,0 +1,50 @@ +package kyverno + +import ( + "flag" + "os" + + "github.com/nirmata/kyverno/pkg/kyverno/validate" + + "github.com/nirmata/kyverno/pkg/kyverno/apply" + + "github.com/nirmata/kyverno/pkg/kyverno/version" + + "github.com/spf13/cobra" +) + +func CLI() { + cli := &cobra.Command{ + Use: "kyverno", + Short: "kyverno manages native policies of Kubernetes", + } + + configureGlog(cli) + + commands := []*cobra.Command{ + version.Command(), + apply.Command(), + validate.Command(), + } + + cli.AddCommand(commands...) + + cli.SilenceUsage = true + + if err := cli.Execute(); err != nil { + os.Exit(1) + } +} + +func configureGlog(cli *cobra.Command) { + flag.Parse() + _ = flag.Set("logtostderr", "true") + + cli.PersistentFlags().AddGoFlagSet(flag.CommandLine) + _ = cli.PersistentFlags().MarkHidden("alsologtostderr") + _ = cli.PersistentFlags().MarkHidden("logtostderr") + _ = cli.PersistentFlags().MarkHidden("log_dir") + _ = cli.PersistentFlags().MarkHidden("log_backtrace_at") + _ = cli.PersistentFlags().MarkHidden("stderrthreshold") + _ = cli.PersistentFlags().MarkHidden("vmodule") +} diff --git a/pkg/kyverno/sanitizedError/error.go b/pkg/kyverno/sanitizedError/error.go new file mode 100644 index 0000000000..3c8ef003f7 --- /dev/null +++ b/pkg/kyverno/sanitizedError/error.go @@ -0,0 +1,20 @@ +package sanitizedError + +type customError struct { + message string +} + +func (c customError) Error() string { + return c.message +} + +func New(message string) error { + return customError{message: message} +} + +func IsErrorSanitized(err error) bool { + if _, ok := err.(customError); !ok { + return false + } + return true +} diff --git a/pkg/kyverno/validate/command.go b/pkg/kyverno/validate/command.go new file mode 100644 index 0000000000..3845626305 --- /dev/null +++ b/pkg/kyverno/validate/command.go @@ -0,0 +1,142 @@ +package validate + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path/filepath" + + "github.com/nirmata/kyverno/pkg/kyverno/sanitizedError" + + "github.com/golang/glog" + + policyvalidate "github.com/nirmata/kyverno/pkg/policy" + + v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" + "github.com/spf13/cobra" + "k8s.io/apimachinery/pkg/util/yaml" +) + +func Command() *cobra.Command { + cmd := &cobra.Command{ + Use: "validate", + Short: "Validates kyverno policies", + Example: "kyverno validate /path/to/policy.yaml /path/to/folderOfPolicies", + RunE: func(cmd *cobra.Command, policyPaths []string) (err error) { + defer func() { + if err != nil { + if !sanitizedError.IsErrorSanitized(err) { + glog.V(4).Info(err) + err = fmt.Errorf("Internal error") + } + } + }() + + policies, err := getPolicies(policyPaths) + if err != nil { + if !sanitizedError.IsErrorSanitized(err) { + return sanitizedError.New("Could not parse policy paths") + } else { + return err + } + } + + for _, policy := range policies { + err = policyvalidate.Validate(*policy) + if err != nil { + fmt.Println("Policy " + policy.Name + " is invalid") + } else { + fmt.Println("Policy " + policy.Name + " is valid") + } + } + + return nil + }, + } + + return cmd +} + +func getPoliciesInDir(path string) ([]*v1.ClusterPolicy, error) { + var policies []*v1.ClusterPolicy + + files, err := ioutil.ReadDir(path) + if err != nil { + return nil, err + } + + for _, file := range files { + if file.IsDir() { + policiesFromDir, err := getPoliciesInDir(filepath.Join(path, file.Name())) + if err != nil { + return nil, err + } + + policies = append(policies, policiesFromDir...) + } else { + policy, err := getPolicy(filepath.Join(path, file.Name())) + if err != nil { + return nil, err + } + + policies = append(policies, policy) + } + } + + return policies, nil +} + +func getPolicies(paths []string) ([]*v1.ClusterPolicy, error) { + var policies = make([]*v1.ClusterPolicy, 0, len(paths)) + for _, path := range paths { + path = filepath.Clean(path) + + fileDesc, err := os.Stat(path) + if err != nil { + return nil, err + } + + if fileDesc.IsDir() { + policiesFromDir, err := getPoliciesInDir(path) + if err != nil { + return nil, err + } + + policies = append(policies, policiesFromDir...) + } else { + policy, err := getPolicy(path) + if err != nil { + return nil, err + } + + policies = append(policies, policy) + } + } + + return policies, nil +} + +func getPolicy(path string) (*v1.ClusterPolicy, error) { + policy := &v1.ClusterPolicy{} + + file, err := ioutil.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("failed to load file: %v", err) + } + + policyBytes, err := yaml.ToJSON(file) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(policyBytes, policy); err != nil { + return nil, sanitizedError.New(fmt.Sprintf("failed to decode policy in %s", path)) + } + + if policy.TypeMeta.Kind != "ClusterPolicy" { + return nil, sanitizedError.New(fmt.Sprintf("resource %v is not a cluster policy", policy.Name)) + } + + return policy, nil +} diff --git a/pkg/kyverno/version/command.go b/pkg/kyverno/version/command.go new file mode 100644 index 0000000000..25d1324f2a --- /dev/null +++ b/pkg/kyverno/version/command.go @@ -0,0 +1,21 @@ +package version + +import ( + "fmt" + + "github.com/nirmata/kyverno/pkg/version" + "github.com/spf13/cobra" +) + +func Command() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Shows current version of kyverno", + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Printf("Version: %s\n", version.BuildVersion) + fmt.Printf("Time: %s\n", version.BuildTime) + fmt.Printf("Git commit ID: %s\n", version.BuildHash) + return nil + }, + } +} diff --git a/pkg/kyverno/version/version.go b/pkg/kyverno/version/version.go deleted file mode 100644 index 8d1f5dfb75..0000000000 --- a/pkg/kyverno/version/version.go +++ /dev/null @@ -1,29 +0,0 @@ -package version - -import ( - "fmt" - "io" - - "github.com/nirmata/kyverno/pkg/version" - "github.com/spf13/cobra" -) - -// NewCmdVersion is a command to display the build version -func NewCmdVersion(cmdOut io.Writer) *cobra.Command { - - versionCmd := &cobra.Command{ - Use: "version", - Short: "", - Run: func(cmd *cobra.Command, args []string) { - showVersion() - }, - } - - return versionCmd -} - -func showVersion() { - fmt.Printf("Version: %s\n", version.BuildVersion) - fmt.Printf("Time: %s\n", version.BuildTime) - fmt.Printf("Git commit ID: %s\n", version.BuildHash) -} diff --git a/pkg/openapi/helper.go b/pkg/openapi/helper.go new file mode 100644 index 0000000000..b04978afb5 --- /dev/null +++ b/pkg/openapi/helper.go @@ -0,0 +1,5 @@ +package openapi + +func GetDefinitionNameFromKind(kind string) string { + return openApiGlobalState.kindToDefinitionName[kind] +} diff --git a/pkg/openapi/validation.go b/pkg/openapi/validation.go index 7f49b91bef..fdc7d99f68 100644 --- a/pkg/openapi/validation.go +++ b/pkg/openapi/validation.go @@ -77,8 +77,14 @@ func ValidatePolicyMutation(policy v1.ClusterPolicy) error { var kindToRules = make(map[string][]v1.Rule) for _, rule := range policy.Spec.Rules { - rule.MatchResources.Selector = nil if rule.HasMutate() { + rule.MatchResources = v1.MatchResources{ + UserInfo: v1.UserInfo{}, + ResourceDescription: v1.ResourceDescription{ + Kinds: rule.MatchResources.Kinds, + }, + } + rule.ExcludeResources = v1.ExcludeResources{} for _, kind := range rule.MatchResources.Kinds { kindToRules[kind] = append(kindToRules[kind], rule) } @@ -157,6 +163,10 @@ func setValidationGlobalState() error { openApiGlobalState.kindToDefinitionName[path[len(path)-1]] = definition.GetName() } + for _, path := range openApiGlobalState.document.GetPaths().GetPath() { + path.GetName() + } + openApiGlobalState.models, err = proto.NewOpenAPIData(openApiGlobalState.document) if err != nil { return err diff --git a/pkg/webhooks/policymutation.go b/pkg/webhooks/policymutation.go index f685771d4e..8081528edd 100644 --- a/pkg/webhooks/policymutation.go +++ b/pkg/webhooks/policymutation.go @@ -31,7 +31,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest) } } // Generate JSON Patches for defaults - patches, updateMsgs := generateJSONPatchesForDefaults(policy, request.Operation) + patches, updateMsgs := generateJSONPatchesForDefaults(policy) if patches != nil { patchType := v1beta1.PatchTypeJSONPatch glog.V(4).Infof("defaulted values %v policy %s", updateMsgs, policy.Name) @@ -50,7 +50,7 @@ func (ws *WebhookServer) handlePolicyMutation(request *v1beta1.AdmissionRequest) } } -func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1beta1.Operation) ([]byte, []string) { +func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy) ([]byte, []string) { var patches [][]byte var updateMsgs []string @@ -66,20 +66,18 @@ func generateJSONPatchesForDefaults(policy *kyverno.ClusterPolicy, operation v1b updateMsgs = append(updateMsgs, updateMsg) } - // TODO(shuting): enable this feature on policy UPDATE - if operation == v1beta1.Create { - patch, errs := generatePodControllerRule(*policy) - if len(errs) > 0 { - var errMsgs []string - for _, err := range errs { - errMsgs = append(errMsgs, err.Error()) - } - glog.Errorf("failed auto generatig rule for pod controllers: %s", errMsgs) - updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";")) + patch, errs := generatePodControllerRule(*policy) + if len(errs) > 0 { + var errMsgs []string + for _, err := range errs { + errMsgs = append(errMsgs, err.Error()) } - - patches = append(patches, patch...) + glog.Errorf("failed auto generating rule for pod controllers: %s", errMsgs) + updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";")) } + + patches = append(patches, patch...) + return utils.JoinPatches(patches), updateMsgs } @@ -170,25 +168,73 @@ func generatePodControllerRule(policy kyverno.ClusterPolicy) (patches [][]byte, return } +func createRuleMap(rules []kyverno.Rule) map[string]kyvernoRule { + var ruleMap = make(map[string]kyvernoRule) + for _, rule := range rules { + var jsonFriendlyStruct kyvernoRule + + jsonFriendlyStruct.Name = rule.Name + + if !reflect.DeepEqual(rule.MatchResources, kyverno.MatchResources{}) { + jsonFriendlyStruct.MatchResources = rule.MatchResources.DeepCopy() + } + + if !reflect.DeepEqual(rule.ExcludeResources, kyverno.ExcludeResources{}) { + jsonFriendlyStruct.ExcludeResources = rule.ExcludeResources.DeepCopy() + } + + if !reflect.DeepEqual(rule.Mutation, kyverno.Mutation{}) { + jsonFriendlyStruct.Mutation = rule.Mutation.DeepCopy() + } + + if !reflect.DeepEqual(rule.Validation, kyverno.Validation{}) { + jsonFriendlyStruct.Validation = rule.Validation.DeepCopy() + } + + ruleMap[rule.Name] = jsonFriendlyStruct + } + return ruleMap +} + // generateRulePatches generates rule for podControllers based on scenario A and C func generateRulePatches(policy kyverno.ClusterPolicy, controllers string) (rulePatches [][]byte, errs []error) { var genRule kyvernoRule insertIdx := len(policy.Spec.Rules) + ruleMap := createRuleMap(policy.Spec.Rules) + var ruleIndex = make(map[string]int) + for index, rule := range policy.Spec.Rules { + ruleIndex[rule.Name] = index + } + for _, rule := range policy.Spec.Rules { + patchPostion := insertIdx + genRule = generateRuleForControllers(rule, controllers) if reflect.DeepEqual(genRule, kyvernoRule{}) { continue } + operation := "add" + if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists { + existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule) + genRuleRaw, _ := json.Marshal(genRule) + + if string(existingAutoGenRuleRaw) == string(genRuleRaw) { + continue + } + operation = "replace" + patchPostion = ruleIndex[genRule.Name] + } + // generate patch bytes jsonPatch := struct { Path string `json:"path"` Op string `json:"op"` Value interface{} `json:"value"` }{ - fmt.Sprintf("/spec/rules/%s", strconv.Itoa(insertIdx)), - "add", + fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), + operation, genRule, } pbytes, err := json.Marshal(jsonPatch) @@ -227,6 +273,10 @@ type kyvernoRule struct { } func generateRuleForControllers(rule kyverno.Rule, controllers string) kyvernoRule { + if strings.HasPrefix(rule.Name, "autogen-") { + return kyvernoRule{} + } + match := rule.MatchResources exclude := rule.ExcludeResources if !utils.ContainsString(match.ResourceDescription.Kinds, "Pod") ||