forked from mystiq/dex
[WIP]: add CRD support
This commit is contained in:
parent
38d0de20e3
commit
146481375e
4 changed files with 342 additions and 6 deletions
|
@ -249,7 +249,7 @@ func (c *client) put(resource, name string, v interface{}) error {
|
|||
return checkHTTPErr(resp, http.StatusOK)
|
||||
}
|
||||
|
||||
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger logrus.FieldLogger) (*client, error) {
|
||||
func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, logger logrus.FieldLogger, apiVersion string) (*client, error) {
|
||||
tlsConfig := cryptopasta.DefaultTLSConfig()
|
||||
data := func(b string, file string) ([]byte, error) {
|
||||
if b != "" {
|
||||
|
@ -325,13 +325,19 @@ func newClient(cluster k8sapi.Cluster, user k8sapi.AuthInfo, namespace string, l
|
|||
}
|
||||
}
|
||||
|
||||
// if the apiVersion is not configured default to `oidc.coreos.com/v1`
|
||||
if apiVersion == "" {
|
||||
apiVersion = "oidc.coreos.com/v1"
|
||||
}
|
||||
|
||||
logger.Infof("kubernetes client apiVersion = %s", apiVersion)
|
||||
// TODO(ericchiang): make API Group and version configurable.
|
||||
return &client{
|
||||
client: &http.Client{Transport: t},
|
||||
baseURL: cluster.Server,
|
||||
hash: func() hash.Hash { return fnv.New64() },
|
||||
namespace: namespace,
|
||||
apiVersion: "oidc.coreos.com/v1",
|
||||
apiVersion: apiVersion,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
|
138
storage/kubernetes/k8sapi/crd_extensions.go
Normal file
138
storage/kubernetes/k8sapi/crd_extensions.go
Normal file
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package k8sapi
|
||||
|
||||
// CustomResourceDefinitionSpec describes how a user wants their resource to appear
|
||||
type CustomResourceDefinitionSpec struct {
|
||||
// Group is the group this resource belongs in
|
||||
Group string `json:"group" protobuf:"bytes,1,opt,name=group"`
|
||||
// Version is the version this resource belongs in
|
||||
Version string `json:"version" protobuf:"bytes,2,opt,name=version"`
|
||||
// Names are the names used to describe this custom resource
|
||||
Names CustomResourceDefinitionNames `json:"names" protobuf:"bytes,3,opt,name=names"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionNames indicates the names to serve this CustomResourceDefinition
|
||||
type CustomResourceDefinitionNames struct {
|
||||
// Plural is the plural name of the resource to serve. It must match the name of the CustomResourceDefinition-registration
|
||||
// too: plural.group and it must be all lowercase.
|
||||
Plural string `json:"plural" protobuf:"bytes,1,opt,name=plural"`
|
||||
// Singular is the singular name of the resource. It must be all lowercase Defaults to lowercased <kind>
|
||||
Singular string `json:"singular,omitempty" protobuf:"bytes,2,opt,name=singular"`
|
||||
// ShortNames are short names for the resource. It must be all lowercase.
|
||||
ShortNames []string `json:"shortNames,omitempty" protobuf:"bytes,3,opt,name=shortNames"`
|
||||
// Kind is the serialized kind of the resource. It is normally CamelCase and singular.
|
||||
Kind string `json:"kind" protobuf:"bytes,4,opt,name=kind"`
|
||||
// ListKind is the serialized kind of the list for this resource. Defaults to <kind>List.
|
||||
ListKind string `json:"listKind,omitempty" protobuf:"bytes,5,opt,name=listKind"`
|
||||
}
|
||||
|
||||
// ResourceScope is an enum defining the different scopes availabe to a custom resource
|
||||
type ResourceScope string
|
||||
|
||||
const (
|
||||
ClusterScoped ResourceScope = "Cluster"
|
||||
NamespaceScoped ResourceScope = "Namespaced"
|
||||
)
|
||||
|
||||
type ConditionStatus string
|
||||
|
||||
// These are valid condition statuses. "ConditionTrue" means a resource is in the condition.
|
||||
// "ConditionFalse" means a resource is not in the condition. "ConditionUnknown" means kubernetes
|
||||
// can't decide if a resource is in the condition or not. In the future, we could add other
|
||||
// intermediate conditions, e.g. ConditionDegraded.
|
||||
const (
|
||||
ConditionTrue ConditionStatus = "True"
|
||||
ConditionFalse ConditionStatus = "False"
|
||||
ConditionUnknown ConditionStatus = "Unknown"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionConditionType is a valid value for CustomResourceDefinitionCondition.Type
|
||||
type CustomResourceDefinitionConditionType string
|
||||
|
||||
const (
|
||||
// Established means that the resource has become active. A resource is established when all names are
|
||||
// accepted without a conflict for the first time. A resource stays established until deleted, even during
|
||||
// a later NamesAccepted due to changed names. Note that not all names can be changed.
|
||||
Established CustomResourceDefinitionConditionType = "Established"
|
||||
// NamesAccepted means the names chosen for this CustomResourceDefinition do not conflict with others in
|
||||
// the group and are therefore accepted.
|
||||
NamesAccepted CustomResourceDefinitionConditionType = "NamesAccepted"
|
||||
// Terminating means that the CustomResourceDefinition has been deleted and is cleaning up.
|
||||
Terminating CustomResourceDefinitionConditionType = "Terminating"
|
||||
)
|
||||
|
||||
// CustomResourceDefinitionCondition contains details for the current condition of this pod.
|
||||
type CustomResourceDefinitionCondition struct {
|
||||
// Type is the type of the condition.
|
||||
Type CustomResourceDefinitionConditionType `json:"type" protobuf:"bytes,1,opt,name=type,casttype=CustomResourceDefinitionConditionType"`
|
||||
// Status is the status of the condition.
|
||||
// Can be True, False, Unknown.
|
||||
Status ConditionStatus `json:"status" protobuf:"bytes,2,opt,name=status,casttype=ConditionStatus"`
|
||||
// Last time the condition transitioned from one status to another.
|
||||
// +optional
|
||||
LastTransitionTime Time `json:"lastTransitionTime,omitempty" protobuf:"bytes,3,opt,name=lastTransitionTime"`
|
||||
// Unique, one-word, CamelCase reason for the condition's last transition.
|
||||
// +optional
|
||||
Reason string `json:"reason,omitempty" protobuf:"bytes,4,opt,name=reason"`
|
||||
// Human-readable message indicating details about last transition.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// CustomResourceDefinitionStatus indicates the state of the CustomResourceDefinition
|
||||
type CustomResourceDefinitionStatus struct {
|
||||
// Conditions indicate state for particular aspects of a CustomResourceDefinition
|
||||
Conditions []CustomResourceDefinitionCondition `json:"conditions" protobuf:"bytes,1,opt,name=conditions"`
|
||||
|
||||
// AcceptedNames are the names that are actually being used to serve discovery
|
||||
// They may be different than the names in spec.
|
||||
AcceptedNames CustomResourceDefinitionNames `json:"acceptedNames" protobuf:"bytes,2,opt,name=acceptedNames"`
|
||||
}
|
||||
|
||||
// CustomResourceCleanupFinalizer is the name of the finalizer which will delete instances of
|
||||
// a CustomResourceDefinition
|
||||
const CustomResourceCleanupFinalizer = "customresourcecleanup.apiextensions.k8s.io"
|
||||
|
||||
// +genclient
|
||||
// +genclient:nonNamespaced
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// CustomResourceDefinition represents a resource that should be exposed on the API server. Its name MUST be in the format
|
||||
// <.spec.name>.<.spec.group>.
|
||||
type CustomResourceDefinition struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Spec describes how the user wants the resources to appear
|
||||
Spec CustomResourceDefinitionSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
|
||||
// Status indicates the actual state of the CustomResourceDefinition
|
||||
Status CustomResourceDefinitionStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// CustomResourceDefinitionList is a list of CustomResourceDefinition objects.
|
||||
type CustomResourceDefinitionList struct {
|
||||
TypeMeta `json:",inline"`
|
||||
ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
// Items individual CustomResourceDefinitions
|
||||
Items []CustomResourceDefinition `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
|
@ -38,6 +38,8 @@ const (
|
|||
type Config struct {
|
||||
InCluster bool `json:"inCluster"`
|
||||
KubeConfigFile string `json:"kubeConfigFile"`
|
||||
APIVersion string `json:"apiVersion"` // API Group and version
|
||||
UseCRD bool `json:"useCRD"` // Flag option to use CRDs instead of TPRs
|
||||
}
|
||||
|
||||
// Open returns a storage using Kubernetes third party resource.
|
||||
|
@ -52,9 +54,9 @@ func (c *Config) Open(logger logrus.FieldLogger) (storage.Storage, error) {
|
|||
// open returns a kubernetes client, initializing the third party resources used
|
||||
// by dex.
|
||||
//
|
||||
// errOnTPRs controls if errors creating the resources cause this method to return
|
||||
// errOnResources controls if errors creating the resources cause this method to return
|
||||
// immediately (used during testing), or if the client will asynchronously retry.
|
||||
func (c *Config) open(logger logrus.FieldLogger, errOnTPRs bool) (*client, error) {
|
||||
func (c *Config) open(logger logrus.FieldLogger, errOnResources bool) (*client, error) {
|
||||
if c.InCluster && (c.KubeConfigFile != "") {
|
||||
return nil, errors.New("cannot specify both 'inCluster' and 'kubeConfigFile'")
|
||||
}
|
||||
|
@ -77,15 +79,46 @@ func (c *Config) open(logger logrus.FieldLogger, errOnTPRs bool) (*client, error
|
|||
return nil, err
|
||||
}
|
||||
|
||||
cli, err := newClient(cluster, user, namespace, logger)
|
||||
cli, err := newClient(cluster, user, namespace, logger, c.APIVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create client: %v", err)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
if c.UseCRD {
|
||||
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 errOnTPRs {
|
||||
if errOnResources {
|
||||
cancel()
|
||||
return nil, fmt.Errorf("failed creating third party resources")
|
||||
}
|
||||
|
@ -144,6 +177,33 @@ func (cli *client) createThirdPartyResources() (ok bool) {
|
|||
return ok
|
||||
}
|
||||
|
||||
// createCustomResourceDefinitions attempts to create the custom resource definitions(CRDs)
|
||||
// required by dex. If the CRDs exist, this information is logged. It logs all errors,
|
||||
// returning true if the CRDs were created successfully.
|
||||
//
|
||||
// TODO: Provide an option to wait for the CRDs to actually be available.
|
||||
func (cli *client) createCustomResourceDefinitions() (ok bool) {
|
||||
ok = true
|
||||
for _, r := range customResourceDefinitions {
|
||||
err := cli.postResource("apiextensions.k8s.io/v1beta1", "", "customresourcedefinition", r)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case storage.ErrAlreadyExists:
|
||||
cli.logger.Infof("custom resource definition already created %s", r.ObjectMeta.Name)
|
||||
case storage.ErrNotFound:
|
||||
cli.logger.Errorf("custom resource definition not found, please enable API group apiextensions.k8s.io/v1beta1")
|
||||
ok = false
|
||||
default:
|
||||
cli.logger.Errorf("creating custom resource definition %s: %v", r.ObjectMeta.Name, err)
|
||||
ok = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
cli.logger.Errorf("create custom resource definition %s", r.ObjectMeta.Name)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
func (cli *client) Close() error {
|
||||
if cli.cancel != nil {
|
||||
cli.cancel()
|
||||
|
|
|
@ -84,6 +84,138 @@ var thirdPartyResources = []k8sapi.ThirdPartyResource{
|
|||
},
|
||||
}
|
||||
|
||||
var crdMeta = k8sapi.TypeMeta{
|
||||
APIVersion: "apiextensions.k8s.io/v1beta1",
|
||||
Kind: "CustomResourceDefinition",
|
||||
}
|
||||
|
||||
const apiGroup = "dex.coreos.com"
|
||||
|
||||
// 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",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: k8sapi.ObjectMeta{
|
||||
Name: "authrequests.dex.coreos.com",
|
||||
},
|
||||
TypeMeta: crdMeta,
|
||||
Spec: k8sapi.CustomResourceDefinitionSpec{
|
||||
Group: apiGroup,
|
||||
Version: "v1",
|
||||
Names: k8sapi.CustomResourceDefinitionNames{
|
||||
Plural: "authrequests",
|
||||
Singular: "authcodrequest",
|
||||
Kind: "AuthRequests",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
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: "signingkeies.dex.coreos.com",
|
||||
},
|
||||
TypeMeta: crdMeta,
|
||||
Spec: k8sapi.CustomResourceDefinitionSpec{
|
||||
Group: apiGroup,
|
||||
Version: "v1",
|
||||
Names: k8sapi.CustomResourceDefinitionNames{
|
||||
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: "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: "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: "connectors.dex.coreos.com",
|
||||
},
|
||||
TypeMeta: crdMeta,
|
||||
Spec: k8sapi.CustomResourceDefinitionSpec{
|
||||
Group: apiGroup,
|
||||
Version: "v1",
|
||||
Names: k8sapi.CustomResourceDefinitionNames{
|
||||
Plural: "connectors",
|
||||
Singular: "connector",
|
||||
Kind: "Connector",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// There will only ever be a single keys resource. Maintain this by setting a
|
||||
// common name.
|
||||
const keysName = "openid-connect-keys"
|
||||
|
|
Loading…
Reference in a new issue