diff --git a/go.mod b/go.mod
index 0d7172b6488a7694109e5a4904e578fc653753f0..e1cfbfb2c2a08d4d35611f3d57c16be07f87663c 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.16
 require (
 	entgo.io/ent v0.8.0
 	github.com/AppsFlyer/go-sundheit v0.4.0
+	github.com/Masterminds/semver v1.5.0
 	github.com/Masterminds/sprig/v3 v3.2.2
 	github.com/beevik/etree v1.1.0
 	github.com/coreos/go-oidc/v3 v3.0.0
diff --git a/go.sum b/go.sum
index 0fd972dde68e7dcf7a5ef0fd56ae722614208e7c..38e520b06d583bc2c2a2b88c512aee72cd09faa6 100644
--- a/go.sum
+++ b/go.sum
@@ -51,6 +51,8 @@ github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q
 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
 github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
 github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
 github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
 github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
diff --git a/scripts/manifests/crds/authcodes.yaml b/scripts/manifests/crds/authcodes.yaml
index 54009253d9a7ae84ddeca6700009ac2f3c364f95..b65920d8f06f71ffe7d493ef3656f3d9413ab9db 100644
--- a/scripts/manifests/crds/authcodes.yaml
+++ b/scripts/manifests/crds/authcodes.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: authcodes.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: AuthCodeList
     plural: authcodes
     singular: authcode
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/authrequests.yaml b/scripts/manifests/crds/authrequests.yaml
index 6c55795768adbd5da500a3fe4eb595d05d503385..f7e08433ab073370ddcaea592bf57e9087f9a1fa 100644
--- a/scripts/manifests/crds/authrequests.yaml
+++ b/scripts/manifests/crds/authrequests.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: authrequests.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: AuthRequestList
     plural: authrequests
     singular: authrequest
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/connectors.yaml b/scripts/manifests/crds/connectors.yaml
index 94e1a276755ec2ab6712942c1d8b455464145697..1c79156ff1edac0d74ac5dd9f1f276299509da05 100644
--- a/scripts/manifests/crds/connectors.yaml
+++ b/scripts/manifests/crds/connectors.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: connectors.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: ConnectorList
     plural: connectors
     singular: connector
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/devicerequests.yaml b/scripts/manifests/crds/devicerequests.yaml
index 9b5b420067922eddccf14868786795273bf43b35..2103f1e4ff7ad4cdecb20b1853d7468b241d7246 100644
--- a/scripts/manifests/crds/devicerequests.yaml
+++ b/scripts/manifests/crds/devicerequests.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: devicerequests.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: DeviceRequestList
     plural: devicerequests
     singular: devicerequest
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/devicetokens.yaml b/scripts/manifests/crds/devicetokens.yaml
index b6ce78dc28c6a271dbaae5acd5dfdce41f856b0c..d478b8096bb6a440b3b478cf015f4ad3b3a2952e 100644
--- a/scripts/manifests/crds/devicetokens.yaml
+++ b/scripts/manifests/crds/devicetokens.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: devicetokens.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: DeviceTokenList
     plural: devicetokens
     singular: devicetoken
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/oauth2clients.yaml b/scripts/manifests/crds/oauth2clients.yaml
index 8b2d7ae39d9db64c139dde55961fdb92a110ad88..ef41e1796bafeb1620e865ee59d31f93d65e57a0 100644
--- a/scripts/manifests/crds/oauth2clients.yaml
+++ b/scripts/manifests/crds/oauth2clients.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: oauth2clients.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: OAuth2ClientList
     plural: oauth2clients
     singular: oauth2client
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/offlinesessionses.yaml b/scripts/manifests/crds/offlinesessionses.yaml
index c51af37dc7b1ff7c4a0bdf9704185ccba6dfc174..e8e5a783e857fb94734f23efd80e053b2b9cba2a 100644
--- a/scripts/manifests/crds/offlinesessionses.yaml
+++ b/scripts/manifests/crds/offlinesessionses.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: offlinesessionses.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: OfflineSessionsList
     plural: offlinesessionses
     singular: offlinesessions
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/passwords.yaml b/scripts/manifests/crds/passwords.yaml
index 65d79cca6c65b0a70014d3085627270075e41a88..e83b855009bcedb6e658eb4e9102469ebd0f5096 100644
--- a/scripts/manifests/crds/passwords.yaml
+++ b/scripts/manifests/crds/passwords.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: passwords.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: PasswordList
     plural: passwords
     singular: password
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/refreshtokens.yaml b/scripts/manifests/crds/refreshtokens.yaml
index 8b20a6bd5568e07487c2f83adbf8b7a25d02eed0..f9364c74f746d269e724ade3ba68af88dbd03d8d 100644
--- a/scripts/manifests/crds/refreshtokens.yaml
+++ b/scripts/manifests/crds/refreshtokens.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: refreshtokens.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: RefreshTokenList
     plural: refreshtokens
     singular: refreshtoken
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/scripts/manifests/crds/signingkeies.yaml b/scripts/manifests/crds/signingkeies.yaml
index 3f8a3838818df8f16f9ed054685f4b87d9ef6ed7..7e8af68df3c2a019708db64cf9bc9467fc2f9e28 100644
--- a/scripts/manifests/crds/signingkeies.yaml
+++ b/scripts/manifests/crds/signingkeies.yaml
@@ -1,4 +1,4 @@
-apiVersion: apiextensions.k8s.io/v1beta1
+apiVersion: apiextensions.k8s.io/v1
 kind: CustomResourceDefinition
 metadata:
   name: signingkeies.dex.coreos.com
@@ -9,4 +9,12 @@ spec:
     listKind: SigningKeyList
     plural: signingkeies
     singular: signingkey
-  version: v1
+  scope: Namespaced
+  versions:
+  - name: v1
+    served: true
+    storage: true
+    schema:
+      openAPIV3Schema:
+        type: object
+        x-kubernetes-preserve-unknown-fields: true
diff --git a/storage/kubernetes/client.go b/storage/kubernetes/client.go
index 593f1c03380021e01f0753238ae9524fb5862c84..da4c382d7742dfd1c2094e7be7bacf50d7871689 100644
--- a/storage/kubernetes/client.go
+++ b/storage/kubernetes/client.go
@@ -22,6 +22,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Masterminds/semver"
 	"github.com/ghodss/yaml"
 	"golang.org/x/net/http2"
 
@@ -47,6 +48,10 @@ type client struct {
 	// API version of the oidc resources. For example "oidc.coreos.com". This is
 	// currently not configurable, but could be in the future.
 	apiVersion string
+	// API version of the custom resource definitions.
+	// Different Kubernetes version requires to create CRD in certain API. It will be discovered automatically on
+	// storage opening.
+	crdAPIVersion string
 
 	// This is called once the client's Close method is called to signal goroutines,
 	// such as the one creating third party resources, to stop.
@@ -195,6 +200,37 @@ func (cli *client) postResource(apiVersion, namespace, resource string, v interf
 	return checkHTTPErr(resp, http.StatusCreated)
 }
 
+func (cli *client) detectKubernetesVersion() error {
+	var version struct{ GitVersion string }
+
+	url := cli.baseURL + "/version"
+	resp, err := cli.client.Get(url)
+	if err != nil {
+		return err
+	}
+
+	defer closeResp(resp)
+	if err := checkHTTPErr(resp, http.StatusOK); err != nil {
+		return err
+	}
+
+	if err := json.NewDecoder(resp.Body).Decode(&version); err != nil {
+		return err
+	}
+
+	clusterVersion, err := semver.NewVersion(version.GitVersion)
+	if err != nil {
+		cli.logger.Warnf("cannot detect Kubernetes version (%s): %v", clusterVersion, err)
+		return nil
+	}
+
+	if clusterVersion.LessThan(semver.MustParse("v1.16.0")) {
+		cli.crdAPIVersion = legacyCRDAPIVersion
+	}
+
+	return nil
+}
+
 func (cli *client) delete(resource, name string) error {
 	url := cli.urlFor(cli.apiVersion, cli.namespace, resource, name)
 	req, err := http.NewRequest("DELETE", url, nil)
@@ -351,11 +387,12 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l
 			Transport: t,
 			Timeout:   15 * time.Second,
 		},
-		baseURL:    cluster.Server,
-		hash:       func() hash.Hash { return fnv.New64() },
-		namespace:  namespace,
-		apiVersion: apiVersion,
-		logger:     logger,
+		baseURL:       cluster.Server,
+		hash:          func() hash.Hash { return fnv.New64() },
+		namespace:     namespace,
+		apiVersion:    apiVersion,
+		crdAPIVersion: crdAPIVersion,
+		logger:        logger,
 	}, nil
 }
 
diff --git a/storage/kubernetes/k8sapi/crd_extensions.go b/storage/kubernetes/k8sapi/crd_extensions.go
index 7a65410bfaa5f63cb1796600fcc536f91e719609..d108865ae5bb874535043ff3403a001a89b4a160 100644
--- a/storage/kubernetes/k8sapi/crd_extensions.go
+++ b/storage/kubernetes/k8sapi/crd_extensions.go
@@ -26,6 +26,15 @@ type CustomResourceDefinitionSpec struct {
 
 	// Scope indicates whether this resource is cluster or namespace scoped.  Default is namespaced
 	Scope ResourceScope `json:"scope" protobuf:"bytes,4,opt,name=scope,casttype=ResourceScope"`
+	// versions is the list of all API versions of the defined custom resource.
+	// Version names are used to compute the order in which served versions are listed in API discovery.
+	// If the version string is "kube-like", it will sort above non "kube-like" version strings, which are ordered
+	// lexicographically. "Kube-like" versions start with a "v", then are followed by a number (the major version),
+	// then optionally the string "alpha" or "beta" and another number (the minor version). These are sorted first
+	// by GA > beta > alpha (where GA is a version with no suffix such as beta or alpha), and then by comparing
+	// major version, then minor version. An example sorted list of versions:
+	// v10, v2, v1, v11beta2, v10beta3, v3beta1, v12alpha1, v11alpha2, foo1, foo10.
+	Versions []CustomResourceDefinitionVersion `json:"versions" protobuf:"bytes,7,rep,name=versions"`
 }
 
 // CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
@@ -139,3 +148,29 @@ type CustomResourceDefinitionList struct {
 	// Items individual CustomResourceDefinitions
 	Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"`
 }
+
+type CustomResourceDefinitionVersion struct {
+	// name is the version name, e.g. “v1”, “v2beta1”, etc.
+	// The custom resources are served under this version at `/apis/<group>/<version>/...` if `served` is true.
+	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
+	// served is a flag enabling/disabling this version from being served via REST APIs
+	Served bool `json:"served" protobuf:"varint,2,opt,name=served"`
+	// storage indicates this version should be used when persisting custom resources to storage.
+	// There must be exactly one version with storage=true.
+	Storage bool `json:"storage" protobuf:"varint,3,opt,name=storage"`
+	// schema describes the schema used for validation, pruning, and defaulting of this version of the custom resource.
+	// +optional
+	Schema *CustomResourceValidation `json:"schema,omitempty" protobuf:"bytes,4,opt,name=schema"`
+}
+
+// CustomResourceValidation is a list of validation methods for CustomResources.
+type CustomResourceValidation struct {
+	// OpenAPIV3Schema is the OpenAPI v3 schema to be validated against.
+	OpenAPIV3Schema *JSONSchemaProps `json:"openAPIV3Schema,omitempty" protobuf:"bytes,1,opt,name=openAPIV3Schema"`
+}
+
+// JSONSchemaProps is a JSON-Schema following Specification Draft 4 (http://json-schema.org/).
+type JSONSchemaProps struct {
+	Type                   string `json:"type,omitempty" protobuf:"bytes,5,opt,name=type"`
+	XPreserveUnknownFields *bool  `json:"x-kubernetes-preserve-unknown-fields,omitempty" protobuf:"bytes,38,opt,name=xKubernetesPreserveUnknownFields"`
+}
diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go
index b670244a0eb11733adefd6df87b6b53674464df1..d6349793e55ab56ce26fdc840c4b6b19a2723629 100644
--- a/storage/kubernetes/storage.go
+++ b/storage/kubernetes/storage.go
@@ -88,6 +88,10 @@ func (c *Config) open(logger log.Logger, waitForResources bool) (*client, error)
 		return nil, fmt.Errorf("create client: %v", err)
 	}
 
+	if err = cli.detectKubernetesVersion(); err != nil {
+		return nil, fmt.Errorf("cannot get kubernetes version: %v", err)
+	}
+
 	ctx, cancel := context.WithCancel(context.Background())
 
 	logger.Info("creating custom Kubernetes resources")
@@ -100,7 +104,6 @@ func (c *Config) open(logger log.Logger, waitForResources bool) (*client, error)
 		// Try to synchronously create the custom resources once. This doesn't mean
 		// they'll immediately be available, but ensures that the client will actually try
 		// once.
-		logger.Errorf("failed creating custom resources: %v", err)
 		go func() {
 			for {
 				if cli.registerCustomResources() {
@@ -136,21 +139,25 @@ func (c *Config) open(logger log.Logger, waitForResources bool) (*client, error)
 // Creating a custom resource does not mean that they'll be immediately available.
 func (cli *client) registerCustomResources() (ok bool) {
 	ok = true
-	length := len(customResourceDefinitions)
+
+	definitions := customResourceDefinitions(cli.crdAPIVersion)
+	length := len(definitions)
+
 	for i := 0; i < length; i++ {
 		var err error
 		var resourceName string
 
-		r := customResourceDefinitions[i]
+		r := definitions[i]
 		var i interface{}
-		cli.logger.Infof("checking if custom resource %s has been created already...", r.ObjectMeta.Name)
+		cli.logger.Infof("checking if custom resource %s has already been created...", r.ObjectMeta.Name)
 		if err := cli.list(r.Spec.Names.Plural, &i); err == nil {
 			cli.logger.Infof("The custom resource %s already available, skipping create", r.ObjectMeta.Name)
 			continue
 		} else {
 			cli.logger.Infof("failed to list custom resource %s, attempting to create: %v", r.ObjectMeta.Name, err)
 		}
-		err = cli.postResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinitions", r)
+
+		err = cli.postResource(cli.crdAPIVersion, "", "customresourcedefinitions", r)
 		resourceName = r.ObjectMeta.Name
 
 		if err != nil {
@@ -177,7 +184,7 @@ func (cli *client) waitForCRDs(ctx context.Context) error {
 	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
 	defer cancel()
 
-	for _, crd := range customResourceDefinitions {
+	for _, crd := range customResourceDefinitions(cli.crdAPIVersion) {
 		for {
 			err := cli.isCRDReady(crd.Name)
 			if err == nil {
@@ -199,7 +206,7 @@ func (cli *client) waitForCRDs(ctx context.Context) error {
 // isCRDReady determines if a CRD is ready by inspecting its conditions.
 func (cli *client) isCRDReady(name string) error {
 	var r k8sapi.CustomResourceDefinition
-	err := cli.getResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinitions", name, &r)
+	err := cli.getResource(cli.crdAPIVersion, "", "customresourcedefinitions", name, &r)
 	if err != nil {
 		return fmt.Errorf("get crd %s: %v", name, err)
 	}
diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go
index bed52736a7e3ad7d9df0e09c8149d7c47608202c..faf4ac57e39c73568c3b2e5609a94173daadc447 100644
--- a/storage/kubernetes/types.go
+++ b/storage/kubernetes/types.go
@@ -10,169 +10,223 @@ import (
 	"github.com/dexidp/dex/storage/kubernetes/k8sapi"
 )
 
-var crdMeta = k8sapi.TypeMeta{
-	APIVersion: "apiextensions.k8s.io/v1beta1",
-	Kind:       "CustomResourceDefinition",
-}
+const (
+	apiGroup = "dex.coreos.com"
 
-const apiGroup = "dex.coreos.com"
+	legacyCRDAPIVersion = "apiextensions.k8s.io/v1beta1"
+	crdAPIVersion       = "apiextensions.k8s.io/v1"
+)
 
 // The set of custom resource definitions required by the storage. These are managed by
 // the storage so it can migrate itself by creating new resources.
-var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "authcodes.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "authcodes",
-				Singular: "authcode",
-				Kind:     "AuthCode",
+func customResourceDefinitions(apiVersion string) []k8sapi.CustomResourceDefinition {
+	crdMeta := k8sapi.TypeMeta{
+		APIVersion: apiVersion,
+		Kind:       "CustomResourceDefinition",
+	}
+
+	var version string
+	var scope k8sapi.ResourceScope
+	var versions []k8sapi.CustomResourceDefinitionVersion
+
+	switch apiVersion {
+	case crdAPIVersion:
+		preserveUnknownFields := true
+		versions = []k8sapi.CustomResourceDefinitionVersion{
+			{
+				Name:    "v1",
+				Served:  true,
+				Storage: true,
+				Schema: &k8sapi.CustomResourceValidation{
+					OpenAPIV3Schema: &k8sapi.JSONSchemaProps{
+						Type:                   "object",
+						XPreserveUnknownFields: &preserveUnknownFields,
+					},
+				},
+			},
+		}
+		scope = k8sapi.NamespaceScoped
+	case legacyCRDAPIVersion:
+		version = "v1"
+	default:
+		panic("unknown apiVersion " + apiVersion)
+	}
+
+	return []k8sapi.CustomResourceDefinition{
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "authcodes.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "authcodes",
+					Singular: "authcode",
+					Kind:     "AuthCode",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "authrequests.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "authrequests",
-				Singular: "authrequest",
-				Kind:     "AuthRequest",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "authrequests.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "authrequests",
+					Singular: "authrequest",
+					Kind:     "AuthRequest",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "oauth2clients.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "oauth2clients",
-				Singular: "oauth2client",
-				Kind:     "OAuth2Client",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "oauth2clients.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "oauth2clients",
+					Singular: "oauth2client",
+					Kind:     "OAuth2Client",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "signingkeies.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				// `signingkeies` is an artifact from the old TPR pluralization.
-				// Users don't directly interact with this value, hence leaving it
-				// as is.
-				Plural:   "signingkeies",
-				Singular: "signingkey",
-				Kind:     "SigningKey",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "signingkeies.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					// `signingkeies` is an artifact from the old TPR pluralization.
+					// Users don't directly interact with this value, hence leaving it
+					// as is.
+					Plural:   "signingkeies",
+					Singular: "signingkey",
+					Kind:     "SigningKey",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "refreshtokens.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "refreshtokens",
-				Singular: "refreshtoken",
-				Kind:     "RefreshToken",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "refreshtokens.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "refreshtokens",
+					Singular: "refreshtoken",
+					Kind:     "RefreshToken",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "passwords.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "passwords",
-				Singular: "password",
-				Kind:     "Password",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "passwords.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "passwords",
+					Singular: "password",
+					Kind:     "Password",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "offlinesessionses.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "offlinesessionses",
-				Singular: "offlinesessions",
-				Kind:     "OfflineSessions",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "offlinesessionses.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "offlinesessionses",
+					Singular: "offlinesessions",
+					Kind:     "OfflineSessions",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "connectors.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "connectors",
-				Singular: "connector",
-				Kind:     "Connector",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "connectors.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "connectors",
+					Singular: "connector",
+					Kind:     "Connector",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "devicerequests.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "devicerequests",
-				Singular: "devicerequest",
-				Kind:     "DeviceRequest",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "devicerequests.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "devicerequests",
+					Singular: "devicerequest",
+					Kind:     "DeviceRequest",
+				},
 			},
 		},
-	},
-	{
-		ObjectMeta: k8sapi.ObjectMeta{
-			Name: "devicetokens.dex.coreos.com",
-		},
-		TypeMeta: crdMeta,
-		Spec: k8sapi.CustomResourceDefinitionSpec{
-			Group:   apiGroup,
-			Version: "v1",
-			Names: k8sapi.CustomResourceDefinitionNames{
-				Plural:   "devicetokens",
-				Singular: "devicetoken",
-				Kind:     "DeviceToken",
+		{
+			ObjectMeta: k8sapi.ObjectMeta{
+				Name: "devicetokens.dex.coreos.com",
+			},
+			TypeMeta: crdMeta,
+			Spec: k8sapi.CustomResourceDefinitionSpec{
+				Group:    apiGroup,
+				Version:  version,
+				Versions: versions,
+				Scope:    scope,
+				Names: k8sapi.CustomResourceDefinitionNames{
+					Plural:   "devicetokens",
+					Singular: "devicetoken",
+					Kind:     "DeviceToken",
+				},
 			},
 		},
-	},
+	}
 }
 
 // There will only ever be a single keys resource. Maintain this by setting a