diff --git a/pkg/registry/cachesize/cachesize.go b/pkg/registry/cachesize/cachesize.go index 9bc360ff84f..eccb73e2fbc 100644 --- a/pkg/registry/cachesize/cachesize.go +++ b/pkg/registry/cachesize/cachesize.go @@ -28,39 +28,41 @@ import ( type Resource string const ( - ClusterRoles Resource = "clusterroles" - ClusterRoleBindings Resource = "clusterrolebindings" - Controllers Resource = "controllers" - Daemonsets Resource = "daemonsets" - Deployments Resource = "deployments" - Endpoints Resource = "endpoints" - HorizontalPodAutoscalers Resource = "horizontalpodautoscalers" - Ingress Resource = "ingress" - PodDisruptionBudget Resource = "poddisruptionbudgets" - PetSet Resource = "petset" - Jobs Resource = "jobs" - LimitRanges Resource = "limitranges" - Namespaces Resource = "namespaces" - NetworkPolicys Resource = "networkpolicies" - Nodes Resource = "nodes" - PersistentVolumes Resource = "persistentvolumes" - PersistentVolumeClaims Resource = "persistentvolumeclaims" - Pods Resource = "pods" - PodTemplates Resource = "podtemplates" - Replicasets Resource = "replicasets" - ResourceQuotas Resource = "resourcequotas" - ScheduledJobs Resource = "scheduledjobs" - Roles Resource = "roles" - RoleBindings Resource = "rolebindings" - Secrets Resource = "secrets" - ServiceAccounts Resource = "serviceaccounts" - Services Resource = "services" + CertificateSigningRequests Resource = "certificatesigningrequests" + ClusterRoles Resource = "clusterroles" + ClusterRoleBindings Resource = "clusterrolebindings" + Controllers Resource = "controllers" + Daemonsets Resource = "daemonsets" + Deployments Resource = "deployments" + Endpoints Resource = "endpoints" + HorizontalPodAutoscalers Resource = "horizontalpodautoscalers" + Ingress Resource = "ingress" + PodDisruptionBudget Resource = "poddisruptionbudgets" + PetSet Resource = "petset" + Jobs Resource = "jobs" + LimitRanges Resource = "limitranges" + Namespaces Resource = "namespaces" + NetworkPolicys Resource = "networkpolicies" + Nodes Resource = "nodes" + PersistentVolumes Resource = "persistentvolumes" + PersistentVolumeClaims Resource = "persistentvolumeclaims" + Pods Resource = "pods" + PodTemplates Resource = "podtemplates" + Replicasets Resource = "replicasets" + ResourceQuotas Resource = "resourcequotas" + ScheduledJobs Resource = "scheduledjobs" + Roles Resource = "roles" + RoleBindings Resource = "rolebindings" + Secrets Resource = "secrets" + ServiceAccounts Resource = "serviceaccounts" + Services Resource = "services" ) var watchCacheSizes map[Resource]int func init() { watchCacheSizes = make(map[Resource]int) + watchCacheSizes[CertificateSigningRequests] = 1000 watchCacheSizes[ClusterRoles] = 100 watchCacheSizes[ClusterRoleBindings] = 100 watchCacheSizes[Controllers] = 100 diff --git a/pkg/registry/certificates/doc.go b/pkg/registry/certificates/doc.go new file mode 100644 index 00000000000..03db7ed3dfe --- /dev/null +++ b/pkg/registry/certificates/doc.go @@ -0,0 +1,19 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates provides Registry interface and its RESTStorage +// implementation for storing CertificateSigningRequest objects. +package certificates diff --git a/pkg/registry/certificates/etcd/etcd.go b/pkg/registry/certificates/etcd/etcd.go new file mode 100644 index 00000000000..5e6ad010237 --- /dev/null +++ b/pkg/registry/certificates/etcd/etcd.go @@ -0,0 +1,106 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 etcd + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/certificates" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/cachesize" + csrregistry "k8s.io/kubernetes/pkg/registry/certificates" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/runtime" +) + +// REST implements a RESTStorage for CertificateSigningRequest against etcd +type REST struct { + *registry.Store +} + +// NewREST returns a registry which will store CertificateSigningRequest in the given helper +func NewREST(opts generic.RESTOptions) (*REST, *StatusREST, *ApprovalREST) { + prefix := "/certificatesigningrequests" + + newListFunc := func() runtime.Object { return &certificates.CertificateSigningRequestList{} } + storageInterface := opts.Decorator(opts.Storage, cachesize.GetWatchCacheSizeByResource(cachesize.CertificateSigningRequests), &certificates.CertificateSigningRequest{}, prefix, csrregistry.Strategy, newListFunc) + + store := ®istry.Store{ + NewFunc: func() runtime.Object { return &certificates.CertificateSigningRequest{} }, + NewListFunc: newListFunc, + KeyRootFunc: func(ctx api.Context) string { + return prefix + }, + KeyFunc: func(ctx api.Context, id string) (string, error) { + return registry.NoNamespaceKeyFunc(ctx, prefix, id) + }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*certificates.CertificateSigningRequest).Name, nil + }, + PredicateFunc: func(label labels.Selector, field fields.Selector) generic.Matcher { + return csrregistry.Matcher(label, field) + }, + QualifiedResource: certificates.Resource("certificatesigningrequests"), + DeleteCollectionWorkers: opts.DeleteCollectionWorkers, + + CreateStrategy: csrregistry.Strategy, + UpdateStrategy: csrregistry.Strategy, + + Storage: storageInterface, + } + + // Subresources use the same store and creation strategy, which only + // allows empty subs. Updates to an existing subresource are handled by + // dedicated strategies. + statusStore := *store + statusStore.UpdateStrategy = csrregistry.StatusStrategy + + approvalStore := *store + approvalStore.UpdateStrategy = csrregistry.ApprovalStrategy + + return &REST{store}, &StatusREST{store: &statusStore}, &ApprovalREST{store: &approvalStore} +} + +// StatusREST implements the REST endpoint for changing the status of a CSR. +type StatusREST struct { + store *registry.Store +} + +func (r *StatusREST) New() runtime.Object { + return &certificates.CertificateSigningRequest{} +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx api.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo) +} + +// ApprovalREST implements the REST endpoint for changing the approval state of a CSR. +type ApprovalREST struct { + store *registry.Store +} + +func (r *ApprovalREST) New() runtime.Object { + return &certificates.CertificateSigningRequest{} +} + +// Update alters the approval subset of an object. +func (r *ApprovalREST) Update(ctx api.Context, name string, objInfo rest.UpdatedObjectInfo) (runtime.Object, bool, error) { + return r.store.Update(ctx, name, objInfo) +} diff --git a/pkg/registry/certificates/registry.go b/pkg/registry/certificates/registry.go new file mode 100644 index 00000000000..2e11d2a186c --- /dev/null +++ b/pkg/registry/certificates/registry.go @@ -0,0 +1,92 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/rest" + "k8s.io/kubernetes/pkg/apis/certificates" + "k8s.io/kubernetes/pkg/watch" +) + +// Registry is an interface for things that know how to store CSRs. +type Registry interface { + ListCSRs(ctx api.Context, options *api.ListOptions) (*certificates.CertificateSigningRequestList, error) + CreateCSR(ctx api.Context, csr *certificates.CertificateSigningRequest) error + UpdateCSR(ctx api.Context, csr *certificates.CertificateSigningRequest) error + GetCSR(ctx api.Context, csrID string) (*certificates.CertificateSigningRequest, error) + DeleteCSR(ctx api.Context, csrID string) error + WatchCSRs(ctx api.Context, options *api.ListOptions) (watch.Interface, error) +} + +// storage puts strong typing around storage calls +type storage struct { + rest.StandardStorage +} + +// NewRegistry returns a new Registry interface for the given Storage. Any mismatched +// types will panic. +func NewRegistry(s rest.StandardStorage) Registry { + return &storage{s} +} + +func (s *storage) ListCSRs(ctx api.Context, options *api.ListOptions) (*certificates.CertificateSigningRequestList, error) { + obj, err := s.List(ctx, options) + if err != nil { + return nil, err + } + + return obj.(*certificates.CertificateSigningRequestList), nil +} + +func (s *storage) CreateCSR(ctx api.Context, csr *certificates.CertificateSigningRequest) error { + // Inject user.Info from request context if available and no + // information was supplied. Don't smash existing user information + // since it should be possible for a broadly-scoped user to request a + // certificate on behalf of a more limited user. + if user, ok := api.UserFrom(ctx); ok { + if csr.Spec.Username == "" { + csr.Spec.Username = user.GetName() + csr.Spec.UID = user.GetUID() + csr.Spec.Groups = user.GetGroups() + } + } + _, err := s.Create(ctx, csr) + return err +} + +func (s *storage) UpdateCSR(ctx api.Context, csr *certificates.CertificateSigningRequest) error { + _, _, err := s.Update(ctx, csr.Name, rest.DefaultUpdatedObjectInfo(csr, api.Scheme)) + return err +} + +func (s *storage) WatchCSRs(ctx api.Context, options *api.ListOptions) (watch.Interface, error) { + return s.Watch(ctx, options) +} + +func (s *storage) GetCSR(ctx api.Context, name string) (*certificates.CertificateSigningRequest, error) { + obj, err := s.Get(ctx, name) + if err != nil { + return nil, err + } + return obj.(*certificates.CertificateSigningRequest), nil +} + +func (s *storage) DeleteCSR(ctx api.Context, name string) error { + _, err := s.Delete(ctx, name, nil) + return err +} diff --git a/pkg/registry/certificates/strategy.go b/pkg/registry/certificates/strategy.go new file mode 100644 index 00000000000..cab3a92b88c --- /dev/null +++ b/pkg/registry/certificates/strategy.go @@ -0,0 +1,177 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 certificates + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/certificates" + "k8s.io/kubernetes/pkg/apis/certificates/validation" + "k8s.io/kubernetes/pkg/fields" + "k8s.io/kubernetes/pkg/labels" + "k8s.io/kubernetes/pkg/registry/generic" + "k8s.io/kubernetes/pkg/runtime" + "k8s.io/kubernetes/pkg/util/validation/field" +) + +// csrStrategy implements behavior for CSRs +type csrStrategy struct { + runtime.ObjectTyper + api.NameGenerator +} + +// csrStrategy is the default logic that applies when creating and updating +// CSR objects. +var Strategy = csrStrategy{api.Scheme, api.SimpleNameGenerator} + +// NamespaceScoped is true for CSRs. +func (csrStrategy) NamespaceScoped() bool { + return false +} + +// AllowCreateOnUpdate is false for CSRs. +func (csrStrategy) AllowCreateOnUpdate() bool { + return false +} + +// PrepareForCreate clears fields that are not allowed to be set by end users +// on creation. Users cannot create any derived information, but we expect +// information about the requesting user to be injected by the registry +// interface. Clear everything else. +// TODO: check these ordering assumptions. better way to inject user info? +func (csrStrategy) PrepareForCreate(obj runtime.Object) { + csr := obj.(*certificates.CertificateSigningRequest) + + // Be explicit that users cannot create pre-approved certificate requests. + csr.Status = certificates.CertificateSigningRequestStatus{} + csr.Status.Conditions = []certificates.CertificateSigningRequestCondition{} +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users +// on update. Certificate requests are immutable after creation except via subresources. +func (csrStrategy) PrepareForUpdate(obj, old runtime.Object) { + newCSR := obj.(*certificates.CertificateSigningRequest) + oldCSR := old.(*certificates.CertificateSigningRequest) + + newCSR.Spec = oldCSR.Spec + newCSR.Status = oldCSR.Status +} + +// Validate validates a new CSR. Validation must check for a correct signature. +func (csrStrategy) Validate(ctx api.Context, obj runtime.Object) field.ErrorList { + csr := obj.(*certificates.CertificateSigningRequest) + return validation.ValidateCertificateSigningRequest(csr) +} + +// Canonicalize normalizes the object after validation (which includes a signature check). +func (csrStrategy) Canonicalize(obj runtime.Object) {} + +// ValidateUpdate is the default update validation for an end user. +func (csrStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + oldCSR := old.(*certificates.CertificateSigningRequest) + newCSR := obj.(*certificates.CertificateSigningRequest) + return validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR) +} + +// If AllowUnconditionalUpdate() is true and the object specified by +// the user does not have a resource version, then generic Update() +// populates it with the latest version. Else, it checks that the +// version specified by the user matches the version of latest etcd +// object. +func (csrStrategy) AllowUnconditionalUpdate() bool { + return true +} + +func (s csrStrategy) Export(obj runtime.Object, exact bool) error { + csr, ok := obj.(*certificates.CertificateSigningRequest) + if !ok { + // unexpected programmer error + return fmt.Errorf("unexpected object: %v", obj) + } + s.PrepareForCreate(obj) + if exact { + return nil + } + // CSRs allow direct subresource edits, we clear them without exact so the CSR value can be reused. + csr.Status = certificates.CertificateSigningRequestStatus{} + return nil +} + +// Storage strategy for the Status subresource +type csrStatusStrategy struct { + csrStrategy +} + +var StatusStrategy = csrStatusStrategy{Strategy} + +func (csrStatusStrategy) PrepareForUpdate(obj, old runtime.Object) { + newCSR := obj.(*certificates.CertificateSigningRequest) + oldCSR := old.(*certificates.CertificateSigningRequest) + + // Updating the Status should only update the Status and not the spec + // or approval conditions. The intent is to separate the concerns of + // approval and certificate issuance. + newCSR.Spec = oldCSR.Spec + newCSR.Status.Conditions = oldCSR.Status.Conditions +} + +func (csrStatusStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest)) +} + +// Canonicalize normalizes the object after validation. +func (csrStatusStrategy) Canonicalize(obj runtime.Object) { +} + +// Storage strategy for the Approval subresource +type csrApprovalStrategy struct { + csrStrategy +} + +var ApprovalStrategy = csrApprovalStrategy{Strategy} + +func (csrApprovalStrategy) PrepareForUpdate(obj, old runtime.Object) { + newCSR := obj.(*certificates.CertificateSigningRequest) + oldCSR := old.(*certificates.CertificateSigningRequest) + + // Updating the approval should only update the conditions. + newCSR.Spec = oldCSR.Spec + oldCSR.Status.Conditions = newCSR.Status.Conditions + newCSR.Status = oldCSR.Status +} + +func (csrApprovalStrategy) ValidateUpdate(ctx api.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateCertificateSigningRequestUpdate(obj.(*certificates.CertificateSigningRequest), old.(*certificates.CertificateSigningRequest)) +} + +// Matcher returns a generic matcher for a given label and field selector. +func Matcher(label labels.Selector, field fields.Selector) generic.Matcher { + return generic.MatcherFunc(func(obj runtime.Object) (bool, error) { + sa, ok := obj.(*certificates.CertificateSigningRequest) + if !ok { + return false, fmt.Errorf("not a CertificateSigningRequest") + } + fields := SelectableFields(sa) + return label.Matches(labels.Set(sa.Labels)) && field.Matches(fields), nil + }) +} + +// SelectableFields returns a label set that can be used for filter selection +func SelectableFields(obj *certificates.CertificateSigningRequest) labels.Set { + return labels.Set{} +}