mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +00:00
Introduce TokenCleaner to clean out expired bootstrap tokens
This commit is contained in:
parent
22d6b868e3
commit
2ee7db4daf
@ -14,6 +14,7 @@ go_test(
|
|||||||
"bootstrapsigner_test.go",
|
"bootstrapsigner_test.go",
|
||||||
"common_test.go",
|
"common_test.go",
|
||||||
"jws_test.go",
|
"jws_test.go",
|
||||||
|
"tokencleaner_test.go",
|
||||||
"util_test.go",
|
"util_test.go",
|
||||||
],
|
],
|
||||||
library = ":go_default_library",
|
library = ":go_default_library",
|
||||||
@ -36,6 +37,7 @@ go_library(
|
|||||||
"bootstrapsigner.go",
|
"bootstrapsigner.go",
|
||||||
"doc.go",
|
"doc.go",
|
||||||
"jws.go",
|
"jws.go",
|
||||||
|
"tokencleaner.go",
|
||||||
"util.go",
|
"util.go",
|
||||||
],
|
],
|
||||||
tags = ["automanaged"],
|
tags = ["automanaged"],
|
||||||
|
118
pkg/controller/bootstrap/tokencleaner.go
Normal file
118
pkg/controller/bootstrap/tokencleaner.go
Normal file
@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/pkg/api"
|
||||||
|
"k8s.io/client-go/pkg/api/v1"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api"
|
||||||
|
"k8s.io/kubernetes/pkg/util/metrics"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenCleanerOptions contains options for the TokenCleaner
|
||||||
|
type TokenCleanerOptions struct {
|
||||||
|
// TokenSecretNamespace string is the namespace for token Secrets.
|
||||||
|
TokenSecretNamespace string
|
||||||
|
|
||||||
|
// SecretResync is the time.Duration at which to fully re-list secrets.
|
||||||
|
// If zero, re-list will be delayed as long as possible
|
||||||
|
SecretResync time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTokenCleanerOptions returns a set of default options for creating a
|
||||||
|
// TokenCleaner
|
||||||
|
func DefaultTokenCleanerOptions() TokenCleanerOptions {
|
||||||
|
return TokenCleanerOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenCleaner is a controller that deletes expired tokens
|
||||||
|
type TokenCleaner struct {
|
||||||
|
tokenSecretNamespace string
|
||||||
|
|
||||||
|
client clientset.Interface
|
||||||
|
|
||||||
|
secrets cache.Store
|
||||||
|
secretsController cache.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTokenCleaner returns a new *NewTokenCleaner.
|
||||||
|
//
|
||||||
|
// TODO: Switch to shared informers
|
||||||
|
func NewTokenCleaner(cl clientset.Interface, options TokenCleanerOptions) *TokenCleaner {
|
||||||
|
e := &TokenCleaner{
|
||||||
|
client: cl,
|
||||||
|
tokenSecretNamespace: options.TokenSecretNamespace,
|
||||||
|
}
|
||||||
|
if cl.Core().RESTClient().GetRateLimiter() != nil {
|
||||||
|
metrics.RegisterMetricAndTrackRateLimiterUsage("token_cleaner", cl.Core().RESTClient().GetRateLimiter())
|
||||||
|
}
|
||||||
|
|
||||||
|
secretSelector := fields.SelectorFromSet(map[string]string{api.SecretTypeField: string(bootstrapapi.SecretTypeBootstrapToken)})
|
||||||
|
e.secrets, e.secretsController = cache.NewInformer(
|
||||||
|
&cache.ListWatch{
|
||||||
|
ListFunc: func(lo metav1.ListOptions) (runtime.Object, error) {
|
||||||
|
lo.FieldSelector = secretSelector.String()
|
||||||
|
return e.client.Core().Secrets(e.tokenSecretNamespace).List(lo)
|
||||||
|
},
|
||||||
|
WatchFunc: func(lo metav1.ListOptions) (watch.Interface, error) {
|
||||||
|
lo.FieldSelector = secretSelector.String()
|
||||||
|
return e.client.Core().Secrets(e.tokenSecretNamespace).Watch(lo)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
&v1.Secret{},
|
||||||
|
options.SecretResync,
|
||||||
|
cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: e.evalSecret,
|
||||||
|
UpdateFunc: func(oldSecret, newSecret interface{}) { e.evalSecret(newSecret) },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run runs controller loops and returns when they are done
|
||||||
|
func (tc *TokenCleaner) Run(stopCh <-chan struct{}) {
|
||||||
|
go tc.secretsController.Run(stopCh)
|
||||||
|
<-stopCh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TokenCleaner) evalSecret(o interface{}) {
|
||||||
|
secret := o.(*v1.Secret)
|
||||||
|
if isSecretExpired(secret) {
|
||||||
|
glog.V(3).Infof("Deleting expired secret %s/%s", secret.Namespace, secret.Name)
|
||||||
|
var options *metav1.DeleteOptions
|
||||||
|
if len(secret.UID) > 0 {
|
||||||
|
options = &metav1.DeleteOptions{Preconditions: &metav1.Preconditions{UID: &secret.UID}}
|
||||||
|
}
|
||||||
|
err := tc.client.Core().Secrets(secret.Namespace).Delete(secret.Name, options)
|
||||||
|
// NotFound isn't a real error (it's already been deleted)
|
||||||
|
// Conflict isn't a real error (the UID precondition failed)
|
||||||
|
if err != nil && !apierrors.IsConflict(err) && !apierrors.IsNotFound(err) {
|
||||||
|
glog.V(3).Infof("Error deleting Secret: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
pkg/controller/bootstrap/tokencleaner_test.go
Normal file
85
pkg/controller/bootstrap/tokencleaner_test.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 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 bootstrap
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/client-go/kubernetes/fake"
|
||||||
|
"k8s.io/client-go/pkg/api"
|
||||||
|
core "k8s.io/client-go/testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
spew.Config.DisableMethods = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTokenCleaner() (*TokenCleaner, *fake.Clientset) {
|
||||||
|
options := DefaultTokenCleanerOptions()
|
||||||
|
cl := fake.NewSimpleClientset()
|
||||||
|
return NewTokenCleaner(cl, options), cl
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanerNoExpiration(t *testing.T) {
|
||||||
|
cleaner, cl := newTokenCleaner()
|
||||||
|
|
||||||
|
secret := newTokenSecret("tokenID", "tokenSecret")
|
||||||
|
cleaner.secrets.Add(secret)
|
||||||
|
|
||||||
|
cleaner.evalSecret(secret)
|
||||||
|
|
||||||
|
expected := []core.Action{}
|
||||||
|
|
||||||
|
verifyActions(t, expected, cl.Actions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanerExpired(t *testing.T) {
|
||||||
|
cleaner, cl := newTokenCleaner()
|
||||||
|
|
||||||
|
secret := newTokenSecret("tokenID", "tokenSecret")
|
||||||
|
addSecretExpiration(secret, timeString(-time.Hour))
|
||||||
|
cleaner.secrets.Add(secret)
|
||||||
|
|
||||||
|
cleaner.evalSecret(secret)
|
||||||
|
|
||||||
|
expected := []core.Action{
|
||||||
|
core.NewDeleteAction(
|
||||||
|
schema.GroupVersionResource{Version: "v1", Resource: "secrets"},
|
||||||
|
api.NamespaceSystem,
|
||||||
|
secret.ObjectMeta.Name),
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyActions(t, expected, cl.Actions())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCleanerNotExpired(t *testing.T) {
|
||||||
|
cleaner, cl := newTokenCleaner()
|
||||||
|
|
||||||
|
secret := newTokenSecret("tokenID", "tokenSecret")
|
||||||
|
addSecretExpiration(secret, timeString(time.Hour))
|
||||||
|
cleaner.secrets.Add(secret)
|
||||||
|
|
||||||
|
cleaner.evalSecret(secret)
|
||||||
|
|
||||||
|
expected := []core.Action{}
|
||||||
|
|
||||||
|
verifyActions(t, expected, cl.Actions())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user