Skip to content

Commit

Permalink
Manage sessionSecretsFile
Browse files Browse the repository at this point in the history
  • Loading branch information
Matt Rogers committed Dec 4, 2018
1 parent a7a65c9 commit 677144e
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 0 deletions.
184 changes: 184 additions & 0 deletions pkg/controller/sessionsecret/session_secret_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package sessionsecret

import (
"fmt"
"strings"
"time"

"github.com/golang/glog"

"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
informers "k8s.io/client-go/informers/core/v1"
kcoreclient "k8s.io/client-go/kubernetes/typed/core/v1"
listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"

legacyv1 "github.com/openshift/api/legacyconfig/v1"
cryptohelpers "github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/crypto"
"github.com/openshift/library-go/pkg/operator/events"
)

const (
sessionSecretNamespace = "openshift-kube-apiserver"
sessionSecretName = "session-secret"
)

var (
scheme = runtime.NewScheme()
codecs = serializer.NewCodecFactory(scheme)
legacySchemeBuilder = runtime.NewSchemeBuilder(legacyv1.InstallLegacy)
)

func init() {
legacySchemeBuilder.AddToScheme(scheme)
}

type SessionSecretController struct {
podLister listers.PodLister
secretLister listers.SecretLister
secretClient kcoreclient.SecretsGetter

secretsHasSynced cache.InformerSynced
podsHasSynced cache.InformerSynced

syncHandler func(serviceKey string) error

secretsQueue workqueue.RateLimitingInterface
eventRecorder events.Recorder
}

func NewSessionSecretController(secrets informers.SecretInformer, pods informers.PodInformer, secretsClient kcoreclient.SecretsGetter, resyncInterval time.Duration, eventRecorder events.Recorder) *SessionSecretController {
sc := &SessionSecretController{
secretsQueue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
}

sc.podLister = pods.Lister()
sc.secretLister = secrets.Lister()

pods.Informer().AddEventHandlerWithResyncPeriod(
cache.FilteringResourceEventHandler{
FilterFunc: isKubeAPIServerPod,
Handler: cache.ResourceEventHandlerFuncs{
AddFunc: sc.enqueueSecret,
},
},
resyncInterval,
)

sc.secretClient = secretsClient
sc.secretsHasSynced = secrets.Informer().HasSynced
sc.podsHasSynced = pods.Informer().HasSynced

sc.syncHandler = sc.syncSecret
sc.eventRecorder = eventRecorder

return sc
}

// Run begins watching and syncing.
func (sc *SessionSecretController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
defer sc.secretsQueue.ShutDown()

// Wait for the stores to fill
if !cache.WaitForCacheSync(stopCh, sc.secretsHasSynced, sc.podsHasSynced) {
return
}

glog.V(4).Infof("Starting workers for SessionSecretController")
for i := 0; i < workers; i++ {
go wait.Until(sc.runWorker, time.Second, stopCh)
}
<-stopCh
glog.V(4).Infof("Shutting down SessionSecretController")
}

// processNextWorkItem deals with one key off the secretsQueue. It returns false when it's time to quit.
func (sc *SessionSecretController) processNextWorkItem() bool {
key, quit := sc.secretsQueue.Get()
if quit {
return false
}
defer sc.secretsQueue.Done(key)

err := sc.syncHandler(key.(string))
if err != nil {
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
sc.eventRecorder.Warningf("CreateSessionSecretFailure", "%v failed with : %v", key, err)
sc.secretsQueue.AddRateLimited(key)
return true
}

sc.secretsQueue.Forget(key)
return true
}

func (sc *SessionSecretController) runWorker() {
for sc.processNextWorkItem() {
}
}

func newSessionSecretsYAML() string {
return runtime.EncodeOrDie(codecs.LegacyCodec(legacyv1.LegacySchemeGroupVersion), &legacyv1.SessionSecrets{
Secrets: []legacyv1.SessionSecret{
{
Authentication: string(cryptohelpers.RandomAuthKeyBits()),
Encryption: string(cryptohelpers.RandomEncKeyBits()),
},
},
})
}

func (sc *SessionSecretController) createSessionSecret() error {
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sessionSecretName,
Namespace: sessionSecretNamespace,
},
Data: map[string][]byte{
"secrets": []byte(newSessionSecretsYAML()),
},
}
_, err := sc.secretClient.Secrets(secret.Namespace).Create(secret)
return err
}

// syncSecret creates the session secret if it doesn't exist.
func (sc *SessionSecretController) syncSecret(key string) error {
namespace, name, err := cache.SplitMetaNamespaceKey(key)
if err != nil {
return err
}

_, err = sc.secretLister.Secrets(namespace).Get(name)
if errors.IsNotFound(err) {
glog.V(4).Infof("creating secret %s/%s", namespace, name)
return sc.createSessionSecret()
}
if err != nil {
return err
}
return nil
}

func (sc *SessionSecretController) enqueueSecret(obj interface{}) {
sc.secretsQueue.Add(sessionSecretNamespace + "/" + sessionSecretName)
}

func isKubeAPIServerPod(obj interface{}) bool {
pod, ok := obj.(*v1.Pod)
if !ok {
return false
}
if strings.HasPrefix(pod.Name, "openshift-kube-apiserver") {
return true
}
return false
}
52 changes: 52 additions & 0 deletions pkg/operator/configobservation/authconfig/observe_sessionsecret.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package authconfig

import (
"github.com/golang/glog"

"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"

"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation"
"github.com/openshift/library-go/pkg/operator/configobserver"
"github.com/openshift/library-go/pkg/operator/events"
)

const (
sessionSecretNamespace = "openshift-kube-apiserver"
sessionSecretName = "session-secret"
sessionSecretPath = "/etc/kubernetes/static-pod-resources/secrets/session-secret/secret"
)

// ObserveSessionSecret sets the oauthConfig sessionSecretsFile if it has not been set and the session secret exists
func ObserveSessionSecret(genericListers configobserver.Listers, recorder events.Recorder, existingConfig map[string]interface{}) (map[string]interface{}, []error) {
listers := genericListers.(configobservation.Listers)
errs := []error{}
observedConfig := map[string]interface{}{}
oauthConfigSessionSecretsFilePath := []string{"oauthConfig", "sessionConfig", "sessionSecretsFile"}

currentSessionSecretsFilePath, _, err := unstructured.NestedString(existingConfig, oauthConfigSessionSecretsFilePath...)
if err != nil {
errs = append(errs, err)
}
if currentSessionSecretsFilePath == sessionSecretPath {
return observedConfig, errs
}

_, err = listers.SecretLister.Secrets(sessionSecretNamespace).Get(sessionSecretName)
if errors.IsNotFound(err) {
glog.Warningf("session secret %s/%s not found", sessionSecretNamespace, sessionSecretName)
return observedConfig, errs
}
if err != nil {
errs = append(errs, err)
return nil, errs
}

err = unstructured.SetNestedField(observedConfig, sessionSecretPath, oauthConfigSessionSecretsFilePath...)
if err != nil {
errs = append(errs, err)
return nil, errs
}

return observedConfig, errs
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package authconfig

import (
"testing"

"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation"

"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
v12 "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
)

func TestObserveSessionSecret(t *testing.T) {
indexer := cache.NewIndexer(cache.MetaNamespaceKeyFunc, cache.Indexers{})
secret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: sessionSecretName,
Namespace: sessionSecretNamespace,
},
}
indexer.Add(secret)

listers := configobservation.Listers{
SecretLister: v12.NewSecretLister(indexer),
}
result, errs := ObserveSessionSecret(listers, nil, map[string]interface{}{})
if len(errs) > 0 {
t.Error("expected len(errs) == 0")
}
secretPath, _, err := unstructured.NestedString(result, "oauthConfig", "sessionConfig", "sessionSecretsFile")
if err != nil {
t.Fatal(err)
}
if secretPath != sessionSecretPath {
t.Errorf("expected oauthConfig.sessionConfig.sessionSecretsFile: %s, got %s", sessionSecretPath, secretPath)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

kubeapiserveroperatorinformers "github.com/openshift/cluster-kube-apiserver-operator/pkg/generated/informers/externalversions"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/authconfig"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/etcd"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/images"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/network"
Expand All @@ -23,6 +24,7 @@ func NewConfigObserver(
operatorClient configobserver.OperatorClient,
operatorConfigInformers kubeapiserveroperatorinformers.SharedInformerFactory,
kubeInformersForKubeSystemNamespace kubeinformers.SharedInformerFactory,
kubeInformersForOpenshiftKubeAPIServerNamespace kubeinformers.SharedInformerFactory,
configInformer configinformers.SharedInformerFactory,
eventRecorder events.Recorder,
) *ConfigObserver {
Expand All @@ -34,6 +36,7 @@ func NewConfigObserver(
ImageConfigLister: configInformer.Config().V1().Images().Lister(),
EndpointsLister: kubeInformersForKubeSystemNamespace.Core().V1().Endpoints().Lister(),
ConfigmapLister: kubeInformersForKubeSystemNamespace.Core().V1().ConfigMaps().Lister(),
SecretLister: kubeInformersForOpenshiftKubeAPIServerNamespace.Core().V1().Secrets().Lister(),
ImageConfigSynced: configInformer.Config().V1().Images().Informer().HasSynced,
PreRunCachesSynced: []cache.InformerSynced{
operatorConfigInformers.Kubeapiserver().V1alpha1().KubeAPIServerOperatorConfigs().Informer().HasSynced,
Expand All @@ -44,6 +47,7 @@ func NewConfigObserver(
etcd.ObserveStorageURLs,
network.ObserveRestrictedCIDRs,
images.ObserveInternalRegistryHostname,
authconfig.ObserveSessionSecret,
),
}

Expand Down
1 change: 1 addition & 0 deletions pkg/operator/configobservation/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Listers struct {
ImageConfigLister configlistersv1.ImageLister
EndpointsLister corelistersv1.EndpointsLister
ConfigmapLister corelistersv1.ConfigMapLister
SecretLister corelistersv1.SecretLister

ImageConfigSynced cache.InformerSynced

Expand Down
35 changes: 35 additions & 0 deletions pkg/operator/crypto/keybits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package crypto

// Taken from origin but could be moved to library-go

import (
"crypto/rand"
"crypto/sha256"
)

const (
sha256KeyLenBits = sha256.BlockSize * 8 // max key size with HMAC SHA256
aes256KeyLenBits = 256 // max key size with AES (AES-256)
)

func RandomAuthKeyBits() []byte {
return randomBits(sha256KeyLenBits)
}

func RandomEncKeyBits() []byte {
return randomBits(aes256KeyLenBits)
}

// randomBits returns a random byte slice with at least the requested bits of entropy.
// Callers should avoid using a value less than 256 unless they have a very good reason.
func randomBits(bits int) []byte {
size := bits / 8
if bits%8 != 0 {
size++
}
b := make([]byte, size)
if _, err := rand.Read(b); err != nil {
panic(err) // rand should never fail
}
return b
}
12 changes: 12 additions & 0 deletions pkg/operator/starter.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
configv1client "github.com/openshift/client-go/config/clientset/versioned"
configinformers "github.com/openshift/client-go/config/informers/externalversions"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/apis/kubeapiserver/v1alpha1"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/controller/sessionsecret"
operatorconfigclient "github.com/openshift/cluster-kube-apiserver-operator/pkg/generated/clientset/versioned"
operatorclientinformers "github.com/openshift/cluster-kube-apiserver-operator/pkg/generated/informers/externalversions"
"github.com/openshift/cluster-kube-apiserver-operator/pkg/operator/configobservation/configobservercontroller"
Expand Down Expand Up @@ -55,10 +56,19 @@ func RunOperator(ctx *controllercmd.ControllerContext) error {
schema.GroupVersionResource{Group: v1alpha1.GroupName, Version: "v1alpha1", Resource: "kubeapiserveroperatorconfigs"},
)

sessionSecretController := sessionsecret.NewSessionSecretController(
kubeInformersForOpenshiftKubeAPIServerNamespace.Core().V1().Secrets(),
kubeInformersForOpenshiftKubeAPIServerNamespace.Core().V1().Pods(),
kubeClient.CoreV1(),
time.Minute,
ctx.EventRecorder,
)

configObserver := configobservercontroller.NewConfigObserver(
staticPodOperatorClient,
operatorConfigInformers,
kubeInformersForKubeSystemNamespace,
kubeInformersForOpenshiftKubeAPIServerNamespace,
configInformers,
ctx.EventRecorder,
)
Expand Down Expand Up @@ -96,6 +106,7 @@ func RunOperator(ctx *controllercmd.ControllerContext) error {
kubeInformersForKubeSystemNamespace.Start(ctx.StopCh)
configInformers.Start(ctx.StopCh)

go sessionSecretController.Run(1, ctx.StopCh)
go staticPodControllers.Run(ctx.StopCh)
go targetConfigReconciler.Run(1, ctx.StopCh)
go configObserver.Run(1, ctx.StopCh)
Expand Down Expand Up @@ -123,4 +134,5 @@ var deploymentSecrets = []string{
"etcd-client",
"kubelet-client",
"serving-cert",
"session-secret",
}

0 comments on commit 677144e

Please sign in to comment.