From 7de166765b7fe9fb431e60c29497cefa85010c49 Mon Sep 17 00:00:00 2001 From: William Petit Date: Thu, 28 Mar 2024 15:53:40 +0100 Subject: [PATCH 1/2] feat(k8s): use secret as shared source for admin private key --- .dockerignore | 5 +- go.mod | 1 + go.sum | 3 + internal/admin/init.go | 34 ++++ internal/admin/server.go | 28 ++- internal/config/integrations.go | 14 +- internal/integration/context.go | 49 ++++++ internal/integration/integration.go | 26 +++ .../integration/kubernetes/integration.go | 159 ++++++++++++++---- internal/integration/kubernetes/keyset.go | 41 +++++ internal/integration/kubernetes/options.go | 16 +- internal/jwk/jwk.go | 12 +- internal/setup/integrations.go | 3 +- .../bouncer-admin/files/admin-key.json | 1 - .../resources/bouncer-admin/files/config.yml | 3 +- .../bouncer-admin/kustomization.yaml | 2 +- .../bouncer-admin/resources/deployment.yaml | 10 ++ .../bouncer-server/resources/deployment.yaml | 4 + 18 files changed, 351 insertions(+), 60 deletions(-) create mode 100644 internal/integration/context.go create mode 100644 internal/integration/kubernetes/keyset.go delete mode 100644 misc/k8s/kustomization/base/resources/bouncer-admin/files/admin-key.json diff --git a/.dockerignore b/.dockerignore index 578fc5c..6cee1d9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,7 @@ /data /bin /.bouncer-token -/.env \ No newline at end of file +/.env +/misc/k8s +/misc/k6s +/misc/grafterm \ No newline at end of file diff --git a/go.mod b/go.mod index 8b7abdf..e17b607 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-chi/chi/v5 v5.0.8 github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/mitchellh/mapstructure v1.4.1 + github.com/oklog/ulid/v2 v2.1.0 github.com/ory/dockertest/v3 v3.10.0 github.com/prometheus/client_golang v1.16.0 github.com/qri-io/jsonschema v0.2.1 diff --git a/go.sum b/go.sum index e01f4c2..99aee0e 100644 --- a/go.sum +++ b/go.sum @@ -410,6 +410,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= @@ -434,6 +436,7 @@ github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuh github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4aNE4= github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/internal/admin/init.go b/internal/admin/init.go index 81b9c4b..fa5fd9b 100644 --- a/internal/admin/init.go +++ b/internal/admin/init.go @@ -3,8 +3,11 @@ package admin import ( "context" + "forge.cadoles.com/cadoles/bouncer/internal/integration" + "forge.cadoles.com/cadoles/bouncer/internal/jwk" "forge.cadoles.com/cadoles/bouncer/internal/setup" "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" ) func (s *Server) initRepositories(ctx context.Context) error { @@ -52,3 +55,34 @@ func (s *Server) initProxyRepository(ctx context.Context) error { return nil } + +func (s *Server) initPrivateKey(ctx context.Context) error { + localKey, err := jwk.LoadOrGenerate(string(s.serverConfig.Auth.PrivateKey), jwk.DefaultKeySize) + if err != nil { + return errors.WithStack(err) + } + + ctx = integration.WithPrivateKey(ctx, localKey) + + key, err := integration.RunOnKeyLoad(ctx, s.integrations) + if err != nil { + return errors.WithStack(err) + } + + if key != nil { + s.privateKey = key + } else { + s.privateKey = localKey + } + + logger.Info(ctx, "using private key", logger.F("keyID", s.privateKey.KeyID())) + + publicKeys, err := jwk.PublicKeySet(s.privateKey) + if err != nil { + return errors.WithStack(err) + } + + s.publicKeys = publicKeys + + return nil +} diff --git a/internal/admin/server.go b/internal/admin/server.go index 2bd2a69..f387fea 100644 --- a/internal/admin/server.go +++ b/internal/admin/server.go @@ -35,6 +35,9 @@ type Server struct { bootstrapConfig config.BootstrapConfig proxyRepository store.ProxyRepository layerRepository store.LayerRepository + + privateKey jwk.Key + publicKeys jwk.Set } func (s *Server) Start(ctx context.Context) (<-chan net.Addr, <-chan error) { @@ -67,6 +70,15 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e return } + if err := s.initPrivateKey(ctx); err != nil { + errs <- errors.WithStack(err) + + return + } + + ctx = integration.WithPrivateKey(ctx, s.privateKey) + ctx = integration.WithPublicKeySet(ctx, s.publicKeys) + if err := integration.RunOnStartup(ctx, s.integrations); err != nil { errs <- errors.WithStack(err) @@ -96,20 +108,6 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e } }() - key, err := jwk.LoadOrGenerate(string(s.serverConfig.Auth.PrivateKey), jwk.DefaultKeySize) - if err != nil { - errs <- errors.WithStack(err) - - return - } - - keys, err := jwk.PublicKeySet(key) - if err != nil { - errs <- errors.WithStack(err) - - return - } - router := chi.NewRouter() if s.serverConfig.HTTP.UseRealIP { @@ -160,7 +158,7 @@ func (s *Server) run(parentCtx context.Context, addrs chan net.Addr, errs chan e router.Route("/api/v1", func(r chi.Router) { r.Group(func(r chi.Router) { r.Use(auth.Middleware( - jwt.NewAuthenticator(keys, string(s.serverConfig.Auth.Issuer), jwt.DefaultAcceptableSkew), + jwt.NewAuthenticator(s.publicKeys, string(s.serverConfig.Auth.Issuer), jwt.DefaultAcceptableSkew), )) r.Route("/proxies", func(r chi.Router) { diff --git a/internal/config/integrations.go b/internal/config/integrations.go index be8b51e..6a6c687 100644 --- a/internal/config/integrations.go +++ b/internal/config/integrations.go @@ -9,10 +9,14 @@ type IntegrationsConfig struct { func NewDefaultIntegrationsConfig() IntegrationsConfig { return IntegrationsConfig{ Kubernetes: KubernetesConfig{ - Enabled: false, - WriterTokenSecret: "", - ReaderTokenSecret: "", - LockTimeout: *NewInterpolatedDuration(30 * time.Second), + Enabled: false, + WriterTokenSecret: "", + WriterTokenSecretNamespace: "", + ReaderTokenSecretNamespace: "", + PrivateKeySecret: "", + PrivateKeySecretNamespace: "", + ReaderTokenSecret: "", + LockTimeout: *NewInterpolatedDuration(30 * time.Second), }, } } @@ -23,5 +27,7 @@ type KubernetesConfig struct { WriterTokenSecretNamespace InterpolatedString `yaml:"writerTokenSecretNamespace"` ReaderTokenSecret InterpolatedString `yaml:"readerTokenSecret"` ReaderTokenSecretNamespace InterpolatedString `yaml:"readerTokenSecretNamespace"` + PrivateKeySecret InterpolatedString `yaml:"privateKeySecret"` + PrivateKeySecretNamespace InterpolatedString `yaml:"privateKeySecretNamespace"` LockTimeout InterpolatedDuration `yaml:"lockTimeout"` } diff --git a/internal/integration/context.go b/internal/integration/context.go new file mode 100644 index 0000000..dbf9aac --- /dev/null +++ b/internal/integration/context.go @@ -0,0 +1,49 @@ +package integration + +import ( + "context" + + "forge.cadoles.com/cadoles/bouncer/internal/jwk" + "github.com/pkg/errors" +) + +var ( + ErrNotFound = errors.New("not found") +) + +type contextKey string + +const ( + ctxPublicKeySet contextKey = "public-key-set" + ctxPrivateKey contextKey = "private-key" +) + +func CtxPublicKeySet(ctx context.Context) (jwk.Set, error) { + return ctxValue[jwk.Set](ctx, ctxPublicKeySet) +} + +func WithPublicKeySet(ctx context.Context, set jwk.Set) context.Context { + return context.WithValue(ctx, ctxPublicKeySet, set) +} + +func CtxPrivateKey(ctx context.Context) (jwk.Key, error) { + return ctxValue[jwk.Key](ctx, ctxPrivateKey) +} + +func WithPrivateKey(ctx context.Context, key jwk.Key) context.Context { + return context.WithValue(ctx, ctxPrivateKey, key) +} + +func ctxValue[T any](ctx context.Context, key contextKey) (T, error) { + raw := ctx.Value(key) + if raw == nil { + return *new(T), errors.WithStack(ErrNotFound) + } + + value, ok := raw.(T) + if !ok { + return *new(T), errors.Errorf("unexpected value type '%T'", raw) + } + + return value, nil +} diff --git a/internal/integration/integration.go b/internal/integration/integration.go index 928048a..c4ad816 100644 --- a/internal/integration/integration.go +++ b/internal/integration/integration.go @@ -3,6 +3,7 @@ package integration import ( "context" + "forge.cadoles.com/cadoles/bouncer/internal/jwk" "github.com/pkg/errors" ) @@ -15,6 +16,11 @@ type OnStartup interface { OnStartup(ctx context.Context) error } +type OnKeyLoad interface { + Integration + OnKeyLoad(ctx context.Context) (jwk.Key, error) +} + func RunOnStartup(ctx context.Context, integrations []Integration) error { for _, it := range integrations { onStartup, ok := it.(OnStartup) @@ -29,3 +35,23 @@ func RunOnStartup(ctx context.Context, integrations []Integration) error { return nil } + +func RunOnKeyLoad(ctx context.Context, integrations []Integration) (jwk.Key, error) { + for _, it := range integrations { + onKeyLoad, ok := it.(OnKeyLoad) + if !ok { + continue + } + + key, err := onKeyLoad.OnKeyLoad(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + if key != nil { + return key, nil + } + } + + return nil, nil +} diff --git a/internal/integration/kubernetes/integration.go b/internal/integration/kubernetes/integration.go index 85a2cd0..8354ac4 100644 --- a/internal/integration/kubernetes/integration.go +++ b/internal/integration/kubernetes/integration.go @@ -2,8 +2,7 @@ package kubernetes import ( "context" - "crypto" - "fmt" + "encoding/json" "os" "forge.cadoles.com/cadoles/bouncer/internal/auth/jwt" @@ -28,6 +27,38 @@ type Integration struct { Options *Options } +// OnKeyLoad implements integration.OnKeyLoad. +func (i *Integration) OnKeyLoad(ctx context.Context) (jwk.Key, error) { + locker := i.Options.Locker + timeout := i.Options.LockTimeout + + var key jwk.Key + err := locker.WithLock(ctx, "bouncer-kubernetes-onkeyload", timeout, func(ctx context.Context) error { + client, err := i.getClient() + if err != nil { + return errors.WithStack(err) + } + + if i.Options.PrivateKeySecret != "" { + sharedPrivateKey, err := i.getSharedPrivateKey(ctx, client, i.Options.PrivateKeySecretNamespace, i.Options.PrivateKeySecret) + if err != nil { + return errors.WithStack(err) + } + + if sharedPrivateKey != nil { + key = sharedPrivateKey + } + } + + return nil + }) + if err != nil { + return nil, errors.WithStack(err) + } + + return key, nil +} + // Integration implements integration.OnStartup. func (i *Integration) Integration() {} @@ -36,12 +67,7 @@ func (i *Integration) OnStartup(ctx context.Context) error { locker := i.Options.Locker timeout := i.Options.LockTimeout err := locker.WithLock(ctx, "bouncer-kubernetes-onstartup", timeout, func(ctx context.Context) error { - config, err := rest.InClusterConfig() - if err != nil { - return errors.WithStack(err) - } - - client, err := kubernetes.NewForConfig(config) + client, err := i.getClient() if err != nil { return errors.WithStack(err) } @@ -67,6 +93,20 @@ func (i *Integration) OnStartup(ctx context.Context) error { return nil } +func (i *Integration) getClient() (*kubernetes.Clientset, error) { + config, err := rest.InClusterConfig() + if err != nil { + return nil, errors.WithStack(err) + } + + client, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, errors.WithStack(err) + } + + return client, nil +} + const ( annotationPublicKey = "bouncer.cadoles.com/public-key" ) @@ -90,23 +130,6 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. logger.Debug(ctx, "generating new token") - key, err := jwk.LoadOrGenerate(i.Options.PrivateKey, jwk.DefaultKeySize) - if err != nil { - return errors.WithStack(err) - } - - publicKey, err := key.PublicKey() - if err != nil { - return errors.WithStack(err) - } - - publicKeyThumbprint, err := publicKey.Thumbprint(crypto.SHA256) - if err != nil { - return errors.WithStack(err) - } - - publicKeyHash := fmt.Sprintf("%x", publicKeyThumbprint) - alreadyExists := true secret, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) if err != nil { @@ -117,8 +140,23 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. } } + privateKey, err := integration.CtxPrivateKey(ctx) + if err != nil { + return errors.WithStack(err) + } + + keySet, err := integration.CtxPublicKeySet(ctx) + if err != nil { + return errors.WithStack(err) + } + + publicKeyThumbprint, err := getKeySetThumbprint(keySet) + if err != nil { + return errors.WithStack(err) + } + if !alreadyExists { - token, err := jwt.GenerateToken(ctx, key, i.Options.Issuer, subject, role) + token, err := jwt.GenerateToken(ctx, privateKey, i.Options.Issuer, subject, role) if err != nil { return errors.WithStack(err) } @@ -128,7 +166,7 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. ObjectMeta: metav1.ObjectMeta{ Name: name, Annotations: map[string]string{ - annotationPublicKey: publicKeyHash, + annotationPublicKey: publicKeyThumbprint, }, }, StringData: map[string]string{ @@ -143,8 +181,8 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. } } else { existingPublicKeyHash, exists := secret.Annotations[annotationPublicKey] - if !exists || publicKeyHash != existingPublicKeyHash { - token, err := jwt.GenerateToken(ctx, key, i.Options.Issuer, subject, role) + if !exists || publicKeyThumbprint != existingPublicKeyHash { + token, err := jwt.GenerateToken(ctx, privateKey, i.Options.Issuer, subject, role) if err != nil { return errors.WithStack(err) } @@ -157,7 +195,7 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. secret.Annotations = make(map[string]string) } - secret.Annotations[annotationPublicKey] = publicKeyHash + secret.Annotations[annotationPublicKey] = publicKeyThumbprint logger.Info(ctx, "updating token secret") @@ -172,6 +210,66 @@ func (i *Integration) upsertTokenSecret(ctx context.Context, client *kubernetes. return nil } +func (i *Integration) getSharedPrivateKey(ctx context.Context, client *kubernetes.Clientset, namespace string, name string) (jwk.Key, error) { + if namespace == "" { + defaultNamespace, err := i.getCurrentNamespace() + if err != nil { + return nil, errors.WithStack(err) + } + + namespace = defaultNamespace + } + + ctx = logger.With(ctx, + logger.F("secretNamespace", namespace), + logger.F("secretName", name), + ) + + logger.Debug(ctx, "searching shared private key from secret") + + secret, err := client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil && !k8serr.IsNotFound(err) { + return nil, errors.WithStack(err) + } + + rawPrivateKey, exists := secret.Data["key"] + + if exists && len(rawPrivateKey) != 0 { + key, err := jwk.ParseKey(rawPrivateKey) + if err != nil { + return nil, errors.WithStack(err) + } + + return key, nil + } + + localKey, err := integration.CtxPrivateKey(ctx) + if err != nil { + return nil, errors.WithStack(err) + } + + rawLocalKey, err := json.Marshal(localKey) + if err != nil { + return nil, errors.WithStack(err) + } + + secret = &v1.Secret{ + Type: v1.SecretTypeOpaque, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Data: map[string][]byte{ + "key": rawLocalKey, + }, + } + + if _, err := client.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{}); err != nil { + return nil, errors.WithStack(err) + } + + return localKey, nil +} + func (i *Integration) getCurrentNamespace() (string, error) { namespace, err := os.ReadFile(namespaceFile) if err != nil { @@ -191,4 +289,5 @@ func NewIntegration(funcs ...OptionFunc) *Integration { var ( _ integration.OnStartup = &Integration{} + _ integration.OnKeyLoad = &Integration{} ) diff --git a/internal/integration/kubernetes/keyset.go b/internal/integration/kubernetes/keyset.go new file mode 100644 index 0000000..f5cce35 --- /dev/null +++ b/internal/integration/kubernetes/keyset.go @@ -0,0 +1,41 @@ +package kubernetes + +import ( + "bytes" + "crypto" + "crypto/sha256" + "fmt" + "slices" + + "forge.cadoles.com/cadoles/bouncer/internal/jwk" + "github.com/pkg/errors" +) + +func getKeySetThumbprint(set jwk.Set) (string, error) { + data := make([][]byte, 0, set.Len()) + + for i := 0; i < set.Len(); i++ { + key, exists := set.Key(i) + if !exists { + continue + } + + thumbprint, err := key.Thumbprint(crypto.SHA256) + if err != nil { + return "", errors.WithStack(err) + } + + data = append(data, thumbprint) + } + + slices.SortFunc(data, bytes.Compare) + + hash := sha256.New() + for _, d := range data { + if _, err := hash.Write(d); err != nil { + return "", errors.WithStack(err) + } + } + + return fmt.Sprintf("%x", hash.Sum(nil)), nil +} diff --git a/internal/integration/kubernetes/options.go b/internal/integration/kubernetes/options.go index 0a9c925..063f405 100644 --- a/internal/integration/kubernetes/options.go +++ b/internal/integration/kubernetes/options.go @@ -12,7 +12,8 @@ type Options struct { WriterTokenSecretNamespace string ReaderTokenSecret string ReaderTokenSecretNamespace string - PrivateKey string + PrivateKeySecret string + PrivateKeySecretNamespace string Issuer string Locker lock.Locker LockTimeout time.Duration @@ -26,7 +27,8 @@ func NewOptions(funcs ...OptionFunc) *Options { WriterTokenSecretNamespace: "", ReaderTokenSecret: "", ReaderTokenSecretNamespace: "", - PrivateKey: "", + PrivateKeySecret: "", + PrivateKeySecretNamespace: "", Issuer: "", Locker: memory.NewLocker(), LockTimeout: 30 * time.Second, @@ -62,9 +64,15 @@ func WithReaderTokenSecretNamespace(namespace string) OptionFunc { } } -func WithPrivateKey(privateKeyFile string) OptionFunc { +func WithPrivateKeySecret(secretName string) OptionFunc { return func(opts *Options) { - opts.PrivateKey = privateKeyFile + opts.PrivateKeySecret = secretName + } +} + +func WithPrivateKeySecretNamespace(namespace string) OptionFunc { + return func(opts *Options) { + opts.PrivateKeySecretNamespace = namespace } } diff --git a/internal/jwk/jwk.go b/internal/jwk/jwk.go index 727b2dd..9928a82 100644 --- a/internal/jwk/jwk.go +++ b/internal/jwk/jwk.go @@ -10,6 +10,7 @@ import ( "github.com/lestrrat-go/jwx/v2/jwa" "github.com/lestrrat-go/jwx/v2/jwk" "github.com/lestrrat-go/jwx/v2/jws" + "github.com/oklog/ulid/v2" "github.com/pkg/errors" ) @@ -23,8 +24,9 @@ type ( ) var ( - FromRaw = jwk.FromRaw - NewSet = jwk.NewSet + FromRaw = jwk.FromRaw + NewSet = jwk.NewSet + ParseKey = jwk.ParseKey ) const AlgorithmKey = jwk.AlgorithmKey @@ -95,6 +97,12 @@ func Generate(size int) (jwk.Key, error) { return nil, errors.WithStack(err) } + keyID := ulid.Make().String() + + if err := key.Set(jwk.KeyIDKey, keyID); err != nil { + return nil, errors.WithStack(err) + } + return key, nil } diff --git a/internal/setup/integrations.go b/internal/setup/integrations.go index 92c5910..c69f0bc 100644 --- a/internal/setup/integrations.go +++ b/internal/setup/integrations.go @@ -35,8 +35,9 @@ func setupKubernetesIntegration(ctx context.Context, conf *config.Config) (*kube kubernetes.WithReaderTokenSecretNamespace(string(conf.Integrations.Kubernetes.ReaderTokenSecretNamespace)), kubernetes.WithWriterTokenSecret(string(conf.Integrations.Kubernetes.WriterTokenSecret)), kubernetes.WithWriterTokenSecretNamespace(string(conf.Integrations.Kubernetes.WriterTokenSecretNamespace)), + kubernetes.WithPrivateKeySecret(string(conf.Integrations.Kubernetes.PrivateKeySecret)), + kubernetes.WithPrivateKeySecretNamespace(string(conf.Integrations.Kubernetes.PrivateKeySecretNamespace)), kubernetes.WithIssuer(string(conf.Admin.Auth.Issuer)), - kubernetes.WithPrivateKey(string(conf.Admin.Auth.PrivateKey)), kubernetes.WithLocker(locker), kubernetes.WithLockTimeout(time.Duration(conf.Integrations.Kubernetes.LockTimeout)), ) diff --git a/misc/k8s/kustomization/base/resources/bouncer-admin/files/admin-key.json b/misc/k8s/kustomization/base/resources/bouncer-admin/files/admin-key.json deleted file mode 100644 index 59025df..0000000 --- a/misc/k8s/kustomization/base/resources/bouncer-admin/files/admin-key.json +++ /dev/null @@ -1 +0,0 @@ -{"d":"JuBw5OsGv3rPgVczxUgtJ6iUQ41LQu4Xpu-t8IKI_z8r-BZBlbndxidPmRlGZASLGL3rhY4qw6_ScFxakrMpCreO1RMU0kqtz--N48BXFnW5tEgr1voyyKP__bPssQNn6PgkoyAd11es7MEKlBff_DtGrcSkVRgU0zDZB-vIU0aNEIZPNw0icbYqc1u_QQNPpBU9cw6P33WHhzvfCVAkZKRszwznhiPM08n1vjpiA7e1kQ8a6OC4IFZBvohkmpmyOq1g1OLRABQ83YPCjGjCAejO-jEWkbLksp6rAl_YYpCvfBAjFV76JuZq4eh5IU82LsSfi3PGYBkhxWuLY779XQ","dp":"gljHOQowGK7fVn2DJizWtgRIDJuKpKnoX2PWNJUbm2WZwcEPZalAkxn7Y-w_reLVJZuRpfKEUMS-Tn3-CwI1ZjCHPqMPTXcoG0Pe2E-Z88jOs9lW4XSOASiiM980VIvkV1xCxDJkN3NsDFQ9j9kRGnKuMnsucCW3AKaU917hXNU","dq":"mqY19JcEBDnzS70_XkAsOKqPzemOScax66b-4N6zrsgeLVlRjHffY9uCAgBWzlxOidRdQN8q23ZJB4fqsKB2w00Iw7Jxx94IoAKGjKDT5iB48Y_kdKLAwSHRTXsqA9GG3po_H_JpP_EqX4TDBYtqQZuBD_tACP9HbLYMi_V2YU8","e":"AQAB","kty":"RSA","n":"sam0X0BGcuFwX8z3Wde8cv2o_zl6A9ghpkT0tCjw8qH3GNWrbAqzncSWdHBzoChBgAbuTOVs-ixYC0KeUhwFdc8Ul-jmKJWFaS8kIr3y4EH62-vLgMuIKfaxbsyUG6KMkJfnftge1jPO4ccddNej9msxcqTxu37dcgstutwtd6QkS9p5RrNbDBc8-Z7SQ4TuxJfP8msXRnCPJ-I44yszGdQF1Np2DXakJHVn8PBrDh3iSFwORw8jxNS4oS0OlBl5aSc0t5XkkaNcSU2a50SKts290w54fl6MPJ1sLnnznLy4uu37-nrfEUvqRLDZL9B1F82RM1dtLIIiN4gnSrMlpQ","p":"wOmFPhAT_wXWzMuwtEdYIer3-CiOWxFKpFL09eEJkJ29MIUchEaoiJaUAghqPxM48llfOVaUaLbFVxmo5U3fyjNMaP-nHMUBwojutykMK-gC2R3J4bQgFWfKbGSL7M7UsextAvpq9iiOuR0LNE-xTfCgPIxHVdPZskO3yx0DkjM","q":"68OGRb0tLRjb_PpkGctcSjEz_vvcyjzxGL-fn4_h4GCw98Xrj6Y4rZ4lfWWRSeDohSvdd-ICSlxvxkQOIOcA0H7jyJcBC0KDs4hX5BRGJNDri3QX0ry4_F1ptAdbfiFgQGqCfMRCr7L60Tfd_6tLczvny7eEBKQNGdj6dLfhgMc","qi":"DFwixyxUDf0REPLLa8hOKieRL95_AH9rbYWzStBOdSjKWra5l0reD6a4bbvAYvl0e8qCcRI6S8Nzpz0BYm4sJL7poVOnjxqvBY3Q9Ppf4Mq8lW39pOCJcqOHIvvYHsMjTC5uwp7Yg2p0GvxuUibbyNL1PXf6WZ_szVP_oSMrCXA"} \ No newline at end of file diff --git a/misc/k8s/kustomization/base/resources/bouncer-admin/files/config.yml b/misc/k8s/kustomization/base/resources/bouncer-admin/files/config.yml index be39024..b78bc19 100644 --- a/misc/k8s/kustomization/base/resources/bouncer-admin/files/config.yml +++ b/misc/k8s/kustomization/base/resources/bouncer-admin/files/config.yml @@ -20,7 +20,7 @@ admin: debug: false auth: issuer: http://127.0.0.1:8081 - privateKey: /etc/bouncer/admin-key.json + privateKey: /var/lib/bouncer/admin-key.json metrics: enabled: true endpoint: /.bouncer/metrics @@ -44,3 +44,4 @@ integrations: enabled: true writerTokenSecret: ${BOUNCER_WRITER_TOKEN_SECRET} readerTokenSecret: ${BOUNCER_READER_TOKEN_SECRET} + privateKeySecret: ${BOUNCER_PRIVATE_KEY_SECRET} diff --git a/misc/k8s/kustomization/base/resources/bouncer-admin/kustomization.yaml b/misc/k8s/kustomization/base/resources/bouncer-admin/kustomization.yaml index 09dceb9..b8eba44 100644 --- a/misc/k8s/kustomization/base/resources/bouncer-admin/kustomization.yaml +++ b/misc/k8s/kustomization/base/resources/bouncer-admin/kustomization.yaml @@ -10,10 +10,10 @@ configMapGenerator: - name: bouncer-admin-config files: - ./files/config.yml - - ./files/admin-key.json - name: bouncer-admin-bootstrap - name: bouncer-admin-env literals: - BOUNCER_LOG_LEVEL=2 - BOUNCER_WRITER_TOKEN_SECRET=bouncer-admin-writer-token - BOUNCER_READER_TOKEN_SECRET=bouncer-admin-reader-token + - BOUNCER_PRIVATE_KEY_SECRET=bouncer-admin-private-key diff --git a/misc/k8s/kustomization/base/resources/bouncer-admin/resources/deployment.yaml b/misc/k8s/kustomization/base/resources/bouncer-admin/resources/deployment.yaml index ea48cfc..3fb297a 100644 --- a/misc/k8s/kustomization/base/resources/bouncer-admin/resources/deployment.yaml +++ b/misc/k8s/kustomization/base/resources/bouncer-admin/resources/deployment.yaml @@ -23,6 +23,10 @@ spec: containers: - name: bouncer-admin image: bouncer + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 command: [ "bouncer", @@ -46,6 +50,8 @@ spec: name: bouncer-admin-config - mountPath: /etc/bouncer/bootstrap.d name: bouncer-admin-bootstrap + - mountPath: /var/lib/bouncer + name: bouncer-admin-var volumes: - name: bouncer-admin-config configMap: @@ -53,3 +59,7 @@ spec: - name: bouncer-admin-bootstrap configMap: name: bouncer-admin-bootstrap + - name: bouncer-admin-var + emptyDir: + sizeLimit: 10Mi + medium: Memory diff --git a/misc/k8s/kustomization/base/resources/bouncer-server/resources/deployment.yaml b/misc/k8s/kustomization/base/resources/bouncer-server/resources/deployment.yaml index 1c714e8..1cd921c 100644 --- a/misc/k8s/kustomization/base/resources/bouncer-server/resources/deployment.yaml +++ b/misc/k8s/kustomization/base/resources/bouncer-server/resources/deployment.yaml @@ -21,6 +21,10 @@ spec: containers: - name: bouncer-server image: bouncer + securityContext: + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 1000 command: [ "bouncer", -- 2.17.1 From cc20bdd2890c35b68a6bbb818c8cc03fc291a9ef Mon Sep 17 00:00:00 2001 From: William Petit Date: Thu, 28 Mar 2024 15:54:59 +0100 Subject: [PATCH 2/2] feat: remove printing of default token --- internal/command/server/admin/run.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/internal/command/server/admin/run.go b/internal/command/server/admin/run.go index 7baf7c8..e45b25a 100644 --- a/internal/command/server/admin/run.go +++ b/internal/command/server/admin/run.go @@ -5,9 +5,7 @@ import ( "strings" "forge.cadoles.com/cadoles/bouncer/internal/admin" - "forge.cadoles.com/cadoles/bouncer/internal/auth/jwt" "forge.cadoles.com/cadoles/bouncer/internal/command/common" - "forge.cadoles.com/cadoles/bouncer/internal/jwk" "forge.cadoles.com/cadoles/bouncer/internal/setup" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -21,11 +19,6 @@ const ( func RunCommand() *cli.Command { flags := append( common.Flags(), - &cli.BoolFlag{ - Name: flagPrintDefaultToken, - Usage: "Generate and print a default writer token in console at startup", - Value: true, - }, ) return &cli.Command{ @@ -49,22 +42,6 @@ func RunCommand() *cli.Command { defer flushSentry() - if printDefaultToken := ctx.Bool(flagPrintDefaultToken); printDefaultToken { - key, err := jwk.Generate(jwk.DefaultKeySize) - if err != nil { - return errors.Wrap(err, "could not generate default key") - } - - token, err := jwt.GenerateToken(ctx.Context, key, string(conf.Admin.Auth.Issuer), "default-admin", jwt.Role(jwt.RoleWriter)) - if err != nil { - return errors.WithStack(err) - } - - logger.SetLevel(logger.LevelInfo) - logger.Info(ctx.Context, "default writer token", logger.F("token", token)) - logger.SetLevel(logger.Level(conf.Logger.Level)) - } - integrations, err := setup.SetupIntegrations(ctx.Context, conf) if err != nil { return errors.Wrap(err, "could not setup integrations") -- 2.17.1