forked from mystiq/dex
storage/kubernetes: add CRD support
This commit is contained in:
parent
146481375e
commit
1311caf864
5 changed files with 113 additions and 91 deletions
|
@ -4,13 +4,68 @@ Dex requires persisting state to perform various tasks such as track refresh tok
|
||||||
|
|
||||||
Storage breaches are serious as they can affect applications that rely on dex. Dex saves sensitive data in its backing storage, including signing keys and bcrypt'd passwords. As such, transport security and database ACLs should both be used, no matter which storage option is chosen.
|
Storage breaches are serious as they can affect applications that rely on dex. Dex saves sensitive data in its backing storage, including signing keys and bcrypt'd passwords. As such, transport security and database ACLs should both be used, no matter which storage option is chosen.
|
||||||
|
|
||||||
## Kubernetes third party resources
|
## Kubernetes custom resource definitions (CRDs)
|
||||||
|
|
||||||
__NOTE:__ Dex requires Kubernetes version 1.4+.
|
__NOTE:__ CRDs are only supported by Kubernetes version 1.7+.
|
||||||
|
|
||||||
Kubernetes third party resources are a way for applications to create new resources types in the Kubernetes API. This allows dex to run on top of an existing Kubernetes cluster without the need for an external database. While this storage may not be appropriate for a large number of users, it's extremely effective for many Kubernetes use cases.
|
Kubernetes [custom resource definitions](crd) are a way for applications to create new resources types in the Kubernetes API. The Custom Resource Definition (CRD) API object was introduced in Kubernetes version 1.7 to replace the Third Party Resource (TPR) extension. CRDs allows dex to run on top of an existing Kubernetes cluster without the need for an external database. While this storage may not be appropriate for a large number of users, it's extremely effective for many Kubernetes use cases.
|
||||||
|
|
||||||
The rest of this section will explore internal details of how dex uses `ThirdPartyResources`. __Admins should not interact with these resources directly__, except when debugging. These resources are only designed to store state and aren't meant to be consumed by humans. For modifying dex's state dynamically see the [API documentation](api.md).
|
The rest of this section will explore internal details of how dex uses CRDs. __Admins should not interact with these resources directly__, except while debugging. These resources are only designed to store state and aren't meant to be consumed by end users. For modifying dex's state dynamically see the [API documentation](api.md).
|
||||||
|
|
||||||
|
The following is an example of the AuthCode resource managed by dex:
|
||||||
|
|
||||||
|
```
|
||||||
|
apiVersion: apiextensions.k8s.io/v1beta1
|
||||||
|
kind: CustomResourceDefinition
|
||||||
|
metadata:
|
||||||
|
creationTimestamp: 2017-09-13T19:56:28Z
|
||||||
|
name: authcodes.dex.coreos.com
|
||||||
|
resourceVersion: "288893"
|
||||||
|
selfLink: /apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/authcodes.dex.coreos.com
|
||||||
|
uid: a1cb72dc-98bd-11e7-8f6a-02d13336a01e
|
||||||
|
spec:
|
||||||
|
group: dex.coreos.com
|
||||||
|
names:
|
||||||
|
kind: AuthCode
|
||||||
|
listKind: AuthCodeList
|
||||||
|
plural: authcodes
|
||||||
|
singular: authcode
|
||||||
|
scope: Namespaced
|
||||||
|
version: v1
|
||||||
|
status:
|
||||||
|
acceptedNames:
|
||||||
|
kind: AuthCode
|
||||||
|
listKind: AuthCodeList
|
||||||
|
plural: authcodes
|
||||||
|
singular: authcode
|
||||||
|
conditions:
|
||||||
|
- lastTransitionTime: null
|
||||||
|
message: no conflicts found
|
||||||
|
reason: NoConflicts
|
||||||
|
status: "True"
|
||||||
|
type: NamesAccepted
|
||||||
|
- lastTransitionTime: 2017-09-13T19:56:28Z
|
||||||
|
message: the initial names have been accepted
|
||||||
|
reason: InitialNamesAccepted
|
||||||
|
status: "True"
|
||||||
|
type: Established
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the `CustomResourceDefinition` is created, custom resources can be created and stored at a namespace level. The CRD type and the custom resources can be queried, deleted, and edited like any other resource using `kubectl`.
|
||||||
|
|
||||||
|
## Kubernetes third party resources(TPRs)
|
||||||
|
|
||||||
|
__NOTE:__ TPRs will be deprecated by Kubernetes version 1.8.
|
||||||
|
|
||||||
|
The default behavior of dex from release v2.7.0 onwards is to utitlize CRDs to manage its custom resources. If users would like to use dex with a Kubernetes version lower than 1.7, they will have to force dex to use TPRs instead of CRDs by setting the `UseTPR` flag in the storage configuration as shown below:
|
||||||
|
|
||||||
|
```
|
||||||
|
storage:
|
||||||
|
type: kubernetes
|
||||||
|
config:
|
||||||
|
kubeConfigFile: kubeconfig
|
||||||
|
useTPR: true
|
||||||
|
```
|
||||||
|
|
||||||
The `ThirdPartyResource` type acts as a description for the new resource a user wishes to create. The following an example of a resource managed by dex:
|
The `ThirdPartyResource` type acts as a description for the new resource a user wishes to create. The following an example of a resource managed by dex:
|
||||||
|
|
||||||
|
@ -166,3 +221,4 @@ Any proposal to add a new implementation must address the following:
|
||||||
[issues-transaction-tests]: https://github.com/coreos/dex/issues/600
|
[issues-transaction-tests]: https://github.com/coreos/dex/issues/600
|
||||||
[k8s-api]: https://github.com/kubernetes/kubernetes/blob/master/docs/devel/api-conventions.md#concurrency-control-and-consistency
|
[k8s-api]: https://github.com/kubernetes/kubernetes/blob/master/docs/devel/api-conventions.md#concurrency-control-and-consistency
|
||||||
[psql-conn-options]: https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters
|
[psql-conn-options]: https://godoc.org/github.com/lib/pq#hdr-Connection_String_Parameters
|
||||||
|
[crd]: https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/
|
||||||
|
|
|
@ -249,7 +249,7 @@ func (c *client) put(resource, name string, v interface{}) error {
|
||||||
return checkHTTPErr(resp, http.StatusOK)
|
return checkHTTPErr(resp, http.StatusOK)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger logrus.FieldLogger, apiVersion string) (*client, error) {
|
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger logrus.FieldLogger, useTPR bool) (*client, error) {
|
||||||
tlsConfig := cryptopasta.DefaultTLSConfig()
|
tlsConfig := cryptopasta.DefaultTLSConfig()
|
||||||
data := func(b string, file string) ([]byte, error) {
|
data := func(b string, file string) ([]byte, error) {
|
||||||
if b != "" {
|
if b != "" {
|
||||||
|
@ -325,13 +325,13 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the apiVersion is not configured default to `oidc.coreos.com/v1`
|
// the API Group and version differ depending on if CRDs or TPRs are used.
|
||||||
if apiVersion == "" {
|
apiVersion := "dex.coreos.com/v1"
|
||||||
|
if useTPR {
|
||||||
apiVersion = "oidc.coreos.com/v1"
|
apiVersion = "oidc.coreos.com/v1"
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("kubernetes client apiVersion = %s", apiVersion)
|
logger.Infof("kubernetes client apiVersion = %s", apiVersion)
|
||||||
// TODO(ericchiang): make API Group and version configurable.
|
|
||||||
return &client{
|
return &client{
|
||||||
client: &http.Client{Transport: t},
|
client: &http.Client{Transport: t},
|
||||||
baseURL: cluster.Server,
|
baseURL: cluster.Server,
|
||||||
|
|
|
@ -47,10 +47,13 @@ type CustomResourceDefinitionNames struct {
|
||||||
type ResourceScope string
|
type ResourceScope string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ClusterScoped ResourceScope = "Cluster"
|
// ClusterScoped is the `cluster` scope for a custom resource.
|
||||||
|
ClusterScoped ResourceScope = "Cluster"
|
||||||
|
// NamespaceScoped is the `namespaced` scope for a custom resource.
|
||||||
NamespaceScoped ResourceScope = "Namespaced"
|
NamespaceScoped ResourceScope = "Namespaced"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ConditionStatus reflects if a resource
|
||||||
type ConditionStatus string
|
type ConditionStatus string
|
||||||
|
|
||||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||||
|
|
|
@ -38,8 +38,7 @@ const (
|
||||||
type Config struct {
|
type Config struct {
|
||||||
InCluster bool `json:"inCluster"`
|
InCluster bool `json:"inCluster"`
|
||||||
KubeConfigFile string `json:"kubeConfigFile"`
|
KubeConfigFile string `json:"kubeConfigFile"`
|
||||||
APIVersion string `json:"apiVersion"` // API Group and version
|
UseTPR bool `json:"useTPR"` // Flag option to use TPRs instead of CRDs
|
||||||
UseCRD bool `json:"useCRD"` // Flag option to use CRDs instead of TPRs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open returns a storage using Kubernetes third party resource.
|
// Open returns a storage using Kubernetes third party resource.
|
||||||
|
@ -79,57 +78,26 @@ func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client,
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cli, err := newClient(cluster, user, namespace, logger, c.APIVersion)
|
cli, err := newClient(cluster, user, namespace, logger, c.UseTPR)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create client: %v", err)
|
return nil, fmt.Errorf("create client: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
if c.UseCRD {
|
if !cli.registerCustomResources(c.UseTPR) {
|
||||||
if !cli.createCustomResourceDefinitions() {
|
|
||||||
if errOnResources {
|
|
||||||
cancel()
|
|
||||||
return nil, fmt.Errorf("failed creating custom resource definitions")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to synchronously create the custom resource definitions once. This doesn't mean
|
|
||||||
// they'll immediately be available, but ensures that the client will actually try
|
|
||||||
// once.
|
|
||||||
logger.Errorf("failed creating custom resource definitions: %v", err)
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
if cli.createCustomResourceDefinitions() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
// If the client is closed, stop trying to create third party resources.
|
|
||||||
cli.cancel = cancel
|
|
||||||
return cli, nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if !cli.createThirdPartyResources() {
|
|
||||||
if errOnResources {
|
if errOnResources {
|
||||||
cancel()
|
cancel()
|
||||||
return nil, fmt.Errorf("failed creating third party resources")
|
return nil, fmt.Errorf("failed creating custom resources")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to synchronously create the third party resources once. This doesn't mean
|
// 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
|
// they'll immediately be available, but ensures that the client will actually try
|
||||||
// once.
|
// once.
|
||||||
logger.Errorf("failed creating third party resources: %v", err)
|
logger.Errorf("failed creating custom resources: %v", err)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
if cli.createThirdPartyResources() {
|
if cli.registerCustomResources(c.UseTPR) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,64 +110,56 @@ func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client,
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the client is closed, stop trying to create third party resources.
|
// If the client is closed, stop trying to create resources.
|
||||||
cli.cancel = cancel
|
cli.cancel = cancel
|
||||||
return cli, nil
|
return cli, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// createThirdPartyResources attempts to create the third party resources dex
|
// registerCustomResources attempts to create the custom resources dex
|
||||||
// requires or identifies that they're already enabled. It logs all errors,
|
// requires or identifies that they're already enabled. This function creates
|
||||||
// returning true if the third party resources were created successfully.
|
// third party resources(TPRs) or custom resource definitions(CRDs) depending
|
||||||
|
// on the `useTPR` flag passed in as an argument.
|
||||||
|
// It logs all errors, returning true if the resources were created successfully.
|
||||||
//
|
//
|
||||||
// Creating a third party resource does not mean that they'll be immediately available.
|
// Creating a custom resource does not mean that they'll be immediately available.
|
||||||
//
|
//
|
||||||
// TODO(ericchiang): Provide an option to wait for the third party resources
|
// TODO(ericchiang): Provide an option to wait for the resources to actually
|
||||||
// to actually be available.
|
// be available.
|
||||||
func (cli *client) createThirdPartyResources() (ok bool) {
|
func (cli *client) registerCustomResources(useTPR bool) (ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
for _, r := range thirdPartyResources {
|
length := len(customResourceDefinitions)
|
||||||
err := cli.postResource("extensions/v1beta1", "", "thirdpartyresources", r)
|
if useTPR {
|
||||||
if err != nil {
|
length = len(thirdPartyResources)
|
||||||
switch err {
|
|
||||||
case storage.ErrAlreadyExists:
|
|
||||||
cli.logger.Infof("third party resource already created %s", r.ObjectMeta.Name)
|
|
||||||
case storage.ErrNotFound:
|
|
||||||
cli.logger.Errorf("third party resources not found, please enable API group extensions/v1beta1")
|
|
||||||
ok = false
|
|
||||||
default:
|
|
||||||
cli.logger.Errorf("creating third party resource %s: %v", r.ObjectMeta.Name, err)
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
cli.logger.Errorf("create third party resource %s", r.ObjectMeta.Name)
|
|
||||||
}
|
}
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// createCustomResourceDefinitions attempts to create the custom resource definitions(CRDs)
|
for i := 0; i < length; i++ {
|
||||||
// required by dex. If the CRDs exist, this information is logged. It logs all errors,
|
var err error
|
||||||
// returning true if the CRDs were created successfully.
|
var resourceName string
|
||||||
//
|
|
||||||
// TODO: Provide an option to wait for the CRDs to actually be available.
|
if useTPR {
|
||||||
func (cli *client) createCustomResourceDefinitions() (ok bool) {
|
r := thirdPartyResources[i]
|
||||||
ok = true
|
err = cli.postResource("extensions/v1beta1", "", "thirdpartyresources", r)
|
||||||
for _, r := range customResourceDefinitions {
|
resourceName = r.ObjectMeta.Name
|
||||||
err := cli.postResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinition", r)
|
} else {
|
||||||
|
r := customResourceDefinitions[i]
|
||||||
|
err = cli.postResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinitions", r)
|
||||||
|
resourceName = r.ObjectMeta.Name
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case storage.ErrAlreadyExists:
|
case storage.ErrAlreadyExists:
|
||||||
cli.logger.Infof("custom resource definition already created %s", r.ObjectMeta.Name)
|
cli.logger.Infof("custom resource already created %s", resourceName)
|
||||||
case storage.ErrNotFound:
|
case storage.ErrNotFound:
|
||||||
cli.logger.Errorf("custom resource definition not found, please enable API group apiextensions.k8s.io/v1beta1")
|
cli.logger.Errorf("custom resources not found, please enable the respective API group")
|
||||||
ok = false
|
ok = false
|
||||||
default:
|
default:
|
||||||
cli.logger.Errorf("creating custom resource definition %s: %v", r.ObjectMeta.Name, err)
|
cli.logger.Errorf("creating custom resource %s: %v", resourceName, err)
|
||||||
ok = false
|
ok = false
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cli.logger.Errorf("create custom resource definition %s", r.ObjectMeta.Name)
|
cli.logger.Errorf("create custom resource %s", resourceName)
|
||||||
}
|
}
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
|
@ -119,8 +119,8 @@ var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Names: k8sapi.CustomResourceDefinitionNames{
|
Names: k8sapi.CustomResourceDefinitionNames{
|
||||||
Plural: "authrequests",
|
Plural: "authrequests",
|
||||||
Singular: "authcodrequest",
|
Singular: "authrequest",
|
||||||
Kind: "AuthRequests",
|
Kind: "AuthRequest",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -135,7 +135,7 @@ var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
|
||||||
Names: k8sapi.CustomResourceDefinitionNames{
|
Names: k8sapi.CustomResourceDefinitionNames{
|
||||||
Plural: "oauth2clients",
|
Plural: "oauth2clients",
|
||||||
Singular: "oauth2client",
|
Singular: "oauth2client",
|
||||||
Kind: "Oauth2Client",
|
Kind: "OAuth2Client",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -148,6 +148,9 @@ var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
|
||||||
Group: apiGroup,
|
Group: apiGroup,
|
||||||
Version: "v1",
|
Version: "v1",
|
||||||
Names: k8sapi.CustomResourceDefinitionNames{
|
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",
|
Plural: "signingkeies",
|
||||||
Singular: "signingkey",
|
Singular: "signingkey",
|
||||||
Kind: "SigningKey",
|
Kind: "SigningKey",
|
||||||
|
@ -195,7 +198,7 @@ var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
|
||||||
Names: k8sapi.CustomResourceDefinitionNames{
|
Names: k8sapi.CustomResourceDefinitionNames{
|
||||||
Plural: "offlinesessionses",
|
Plural: "offlinesessionses",
|
||||||
Singular: "offlinesessions",
|
Singular: "offlinesessions",
|
||||||
Kind: "OfflineSessions",
|
Kind: "OfflineSession",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue