Skip to content

Commit

Permalink
controlplane: Store JWK keys in k8s secret
Browse files Browse the repository at this point in the history
Today, the JWK is initialized by the controlplane when loading,
and stored in-memory.
To support multiple controlpanes with the same JWK key,
we change the controlplane authz manager to ready the JWK key
from a secret, instead of generating it.manager
The secret is created by the (soon to be leader elected) control manager.

Signed-off-by: Or Ozeri <[email protected]>
  • Loading branch information
orozery committed Jun 19, 2024
1 parent 3443ecb commit f34049c
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 43 deletions.
14 changes: 9 additions & 5 deletions cmd/cl-controlplane/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ func (o *Options) Run() error {
namespace: {},
},
},
&v1.Secret{}: {
Namespaces: map[string]cache.Config{
namespace: {},
},
},
},
},
Scheme: scheme,
Expand All @@ -177,11 +182,7 @@ func (o *Options) Run() error {
controlplaneServerListenAddress := fmt.Sprintf("0.0.0.0:%d", api.ListenPort)
grpcServer := grpc.NewServer("controlplane-grpc", controlplaneCertData.ServerConfig())

authzManager, err := authz.NewManager(mgr.GetClient(), namespace)
if err != nil {
return fmt.Errorf("cannot create authorization manager: %w", err)
}

authzManager := authz.NewManager(mgr.GetClient(), namespace)
peerCertsWatcher.AddConsumer(authzManager)

err = authz.CreateControllers(authzManager, mgr)
Expand All @@ -193,6 +194,9 @@ func (o *Options) Run() error {

controlManager := control.NewManager(mgr.GetClient(), namespace)
peerCertsWatcher.AddConsumer(controlManager)
if err := controlManager.CreateJWKSSecret(context.Background()); err != nil {
return fmt.Errorf("cannot create JWKS secret: %w", err)
}

err = control.CreateControllers(controlManager, mgr)
if err != nil {
Expand Down
10 changes: 10 additions & 0 deletions config/operator/rbac/role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ rules:
- get
- list
- watch
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- get
- list
- update
- watch
- apiGroups:
- ""
resources:
Expand Down
3 changes: 3 additions & 0 deletions pkg/bootstrap/platform/k8s.go
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ rules:
- apiGroups: [""]
resources: ["services"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get", "list", "watch", "create", "update"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch", "create", "delete", "update"]
Expand Down
16 changes: 15 additions & 1 deletion pkg/controlplane/authz/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,21 @@ func CreateControllers(mgr *Manager, controllerManager ctrl.Manager) error {
AddHandler: func(ctx context.Context, object any) error {
return nil
},
DeleteHandler: func(ctx context.Context, name types.NamespacedName) error {
DeleteHandler: func(_ context.Context, _ types.NamespacedName) error {
return nil
},
})
if err != nil {
return err
}

err = controller.AddToManager(controllerManager, &controller.Spec{
Name: "authz.secret",
Object: &v1.Secret{},
AddHandler: func(_ context.Context, object any) error {
return mgr.addSecret(object.(*v1.Secret))
},
DeleteHandler: func(context.Context, types.NamespacedName) error {
return nil
},
})
Expand Down
76 changes: 51 additions & 25 deletions pkg/controlplane/authz/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ package authz

import (
"context"
"crypto/rand"
"crypto/rsa"
"fmt"
"sync"
"time"
Expand All @@ -33,6 +31,7 @@ import (
"github.com/clusterlink-net/clusterlink/pkg/apis/clusterlink.net/v1alpha1"
cpapi "github.com/clusterlink-net/clusterlink/pkg/controlplane/api"
"github.com/clusterlink-net/clusterlink/pkg/controlplane/authz/connectivitypdp"
"github.com/clusterlink-net/clusterlink/pkg/controlplane/control"
"github.com/clusterlink-net/clusterlink/pkg/controlplane/peer"
"github.com/clusterlink-net/clusterlink/pkg/util/tls"
)
Expand Down Expand Up @@ -109,6 +108,7 @@ type Manager struct {
ipToPod map[string]types.NamespacedName
podList map[types.NamespacedName]podInfo

jwksLock sync.RWMutex
jwkSignKey jwk.Key
jwkVerifyKey jwk.Key

Expand Down Expand Up @@ -176,6 +176,35 @@ func (m *Manager) addPod(pod *v1.Pod) {
}
}

// addSecret adds a new secret.
func (m *Manager) addSecret(secret *v1.Secret) error {
if secret.Namespace != m.namespace || secret.Name != control.JWKSecretName {
return nil
}

privateKey, err := control.ParseJWKSSecret(secret)
if err != nil {
return fmt.Errorf("cannot parse JWKS secret: %w", err)
}

jwkSignKey, err := jwk.New(privateKey)
if err != nil {
return fmt.Errorf("unable to create JWK signing key: %w", err)
}

jwkVerifyKey, err := jwk.New(privateKey.PublicKey)
if err != nil {
return fmt.Errorf("unable to create JWK verifing key: %w", err)
}

m.jwksLock.Lock()
defer m.jwksLock.Unlock()
m.jwkSignKey = jwkSignKey
m.jwkVerifyKey = jwkVerifyKey

return nil
}

// getPodInfoByIP returns the information about the Pod with the specified IP address.
func (m *Manager) getPodInfoByIP(ip string) *podInfo {
m.podLock.RLock()
Expand Down Expand Up @@ -292,8 +321,16 @@ func (m *Manager) authorizeEgress(ctx context.Context, req *egressAuthorizationR
func (m *Manager) parseAuthorizationHeader(token string) (string, error) {
m.logger.Debug("Parsing access token.")

m.jwksLock.RLock()
jwkVerifyKey := m.jwkVerifyKey
m.jwksLock.RUnlock()

if jwkVerifyKey == nil {
return "", fmt.Errorf("jwk verify key undefined")
}

parsedToken, err := jwt.ParseString(
token, jwt.WithVerify(cpapi.JWTSignatureAlgorithm, m.jwkVerifyKey), jwt.WithValidate(true))
token, jwt.WithVerify(cpapi.JWTSignatureAlgorithm, jwkVerifyKey), jwt.WithValidate(true))
if err != nil {
return "", err
}
Expand Down Expand Up @@ -369,8 +406,16 @@ func (m *Manager) authorizeIngress(
return nil, fmt.Errorf("unable to generate access token: %w", err)
}

m.jwksLock.RLock()
jwkSignKey := m.jwkSignKey
m.jwksLock.RUnlock()

if jwkSignKey == nil {
return nil, fmt.Errorf("jwk sign key undefined")
}

// sign access token
signed, err := jwt.Sign(token, cpapi.JWTSignatureAlgorithm, m.jwkSignKey)
signed, err := jwt.Sign(token, cpapi.JWTSignatureAlgorithm, jwkSignKey)
if err != nil {
return nil, fmt.Errorf("unable to sign access token: %w", err)
}
Expand Down Expand Up @@ -411,34 +456,15 @@ func (m *Manager) SetPeerCertificates(peerTLS *tls.ParsedCertData, _ *tls.RawCer
}

// NewManager returns a new authorization manager.
func NewManager(cl client.Client, namespace string) (*Manager, error) {
// generate RSA key-pair for JWT signing
// TODO: instead of generating, read from k8s secret
rsaKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, fmt.Errorf("unable to generate RSA keys: %w", err)
}

jwkSignKey, err := jwk.New(rsaKey)
if err != nil {
return nil, fmt.Errorf("unable to create JWK signing key: %w", err)
}

jwkVerifyKey, err := jwk.New(rsaKey.PublicKey)
if err != nil {
return nil, fmt.Errorf("unable to create JWK verifing key: %w", err)
}

func NewManager(cl client.Client, namespace string) *Manager {
return &Manager{
client: cl,
namespace: namespace,
connectivityPDP: connectivitypdp.NewPDP(),
loadBalancer: NewLoadBalancer(),
peerClient: make(map[string]*peer.Client),
jwkSignKey: jwkSignKey,
jwkVerifyKey: jwkVerifyKey,
ipToPod: make(map[string]types.NamespacedName),
podList: make(map[types.NamespacedName]podInfo),
logger: logrus.WithField("component", "controlplane.authz.manager"),
}, nil
}
}
18 changes: 15 additions & 3 deletions pkg/controlplane/control/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func CreateControllers(mgr *Manager, controllerManager ctrl.Manager) error {
Name: "control.export",
Object: &v1alpha1.Export{},
AddHandler: func(ctx context.Context, object any) error {
return mgr.AddExport(ctx, object.(*v1alpha1.Export))
return mgr.addExport(ctx, object.(*v1alpha1.Export))
},
DeleteHandler: func(ctx context.Context, name types.NamespacedName) error {
return nil
Expand All @@ -75,9 +75,21 @@ func CreateControllers(mgr *Manager, controllerManager ctrl.Manager) error {
Name: "control.import",
Object: &v1alpha1.Import{},
AddHandler: func(ctx context.Context, object any) error {
return mgr.AddImport(ctx, object.(*v1alpha1.Import))
return mgr.addImport(ctx, object.(*v1alpha1.Import))
},
DeleteHandler: mgr.DeleteImport,
DeleteHandler: mgr.deleteImport,
})
if err != nil {
return err
}

err = controller.AddToManager(controllerManager, &controller.Spec{
Name: "control.secret",
Object: &v1.Secret{},
AddHandler: func(ctx context.Context, object any) error {
return mgr.addSecret(ctx, object.(*v1.Secret))
},
DeleteHandler: mgr.deleteSecret,
})
if err != nil {
return err
Expand Down
Loading

0 comments on commit f34049c

Please sign in to comment.