mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-06 02:09:15 +02:00
Add new config writer options mechanism
This moves the encryption cipher parameter out of the WriteConfig() method in the ConfigWriter interface and into a new ConfigWriterOptions struct. Options (e.g. cipher) can be applied to an existing ConfigWriter to allow customizing implementation-specific behavior.
This commit is contained in:
parent
2177cef346
commit
ae5daafbc8
8 changed files with 93 additions and 44 deletions
|
@ -104,7 +104,7 @@ func (svc *Source) updateAndSave(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
err = SaveBootstrapConfig(ctx, svc.writer, cfg, svc.fileCipher)
|
err = SaveBootstrapConfig(ctx, svc.writer, cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Ctx(ctx).Error().Err(err).
|
log.Ctx(ctx).Error().Err(err).
|
||||||
Msg("failed to save bootstrap config to file, note it may prevent Pomerium from starting up in case of connectivity issues")
|
Msg("failed to save bootstrap config to file, note it may prevent Pomerium from starting up in case of connectivity issues")
|
||||||
|
|
|
@ -42,8 +42,8 @@ func LoadBootstrapConfigFromFile(fp string, cipher cipher.AEAD) (*cluster_api.Bo
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveBootstrapConfig saves the bootstrap configuration to a file.
|
// SaveBootstrapConfig saves the bootstrap configuration to a file.
|
||||||
func SaveBootstrapConfig(ctx context.Context, writer writers.ConfigWriter, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error {
|
func SaveBootstrapConfig(ctx context.Context, writer writers.ConfigWriter, src *cluster_api.BootstrapConfig) error {
|
||||||
err := writer.WriteConfig(ctx, src, cipher)
|
err := writer.WriteConfig(ctx, src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
health.ReportError(health.ZeroBootstrapConfigSave, err)
|
health.ReportError(health.ZeroBootstrapConfigSave, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,6 +55,12 @@ func New(secret []byte, fileCachePath *string, writer writers.ConfigWriter, api
|
||||||
return nil, fmt.Errorf("init cypher: %w", err)
|
return nil, fmt.Errorf("init cypher: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if writer != nil {
|
||||||
|
writer = writer.WithOptions(writers.ConfigWriterOptions{
|
||||||
|
Cipher: cipher,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
svc := &Source{
|
svc := &Source{
|
||||||
api: api,
|
api: api,
|
||||||
source: source{ready: make(chan struct{})},
|
source: source{ready: make(chan struct{})},
|
||||||
|
|
|
@ -2,7 +2,6 @@ package filesystem
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/cipher"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -14,30 +13,42 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
writers.RegisterBuilder("file", func(uri *url.URL) (writers.ConfigWriter, error) {
|
writers.RegisterBuilder("file", newFileWriter)
|
||||||
if uri.Host != "" {
|
}
|
||||||
// prevent the common mistake of "file://path/to/file"
|
|
||||||
return nil, fmt.Errorf(`invalid file uri %q (did you mean "file:///%s%s"?)`, uri.String(), uri.Host, uri.Path)
|
func newFileWriter(uri *url.URL) (writers.ConfigWriter, error) {
|
||||||
}
|
if uri.Host != "" {
|
||||||
return &fileWriter{
|
// prevent the common mistake of "file://path/to/file"
|
||||||
filePath: uri.Path,
|
return nil, fmt.Errorf(`invalid file uri %q (did you mean "file:///%s%s"?)`, uri.String(), uri.Host, uri.Path)
|
||||||
}, nil
|
}
|
||||||
})
|
return &fileWriter{
|
||||||
|
filePath: uri.Path,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type fileWriter struct {
|
type fileWriter struct {
|
||||||
|
opts writers.ConfigWriterOptions
|
||||||
filePath string
|
filePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithOptions implements writers.ConfigWriter.
|
||||||
|
func (w *fileWriter) WithOptions(opts writers.ConfigWriterOptions) writers.ConfigWriter {
|
||||||
|
clone := *w
|
||||||
|
clone.opts = opts
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
// WriteConfig implements ConfigWriter.
|
// WriteConfig implements ConfigWriter.
|
||||||
func (w *fileWriter) WriteConfig(_ context.Context, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error {
|
func (w *fileWriter) WriteConfig(_ context.Context, src *cluster_api.BootstrapConfig) error {
|
||||||
plaintext, err := json.Marshal(src)
|
data, err := json.Marshal(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("marshal file config: %w", err)
|
return fmt.Errorf("marshal file config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ciphertext := cryptutil.Encrypt(cipher, plaintext, nil)
|
if w.opts.Cipher != nil {
|
||||||
err = os.WriteFile(w.filePath, ciphertext, 0o600)
|
data = cryptutil.Encrypt(w.opts.Cipher, data, nil)
|
||||||
|
}
|
||||||
|
err = os.WriteFile(w.filePath, data, 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("write bootstrap config: %w", err)
|
return fmt.Errorf("write bootstrap config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,10 @@ func TestFileWriter(t *testing.T) {
|
||||||
writer, err := writers.NewForURI(fmt.Sprintf("file://%s", fd.Name()))
|
writer, err := writers.NewForURI(fmt.Sprintf("file://%s", fd.Name()))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, bootstrap.SaveBootstrapConfig(context.Background(), writer, &src, cipher))
|
writer = writer.WithOptions(writers.ConfigWriterOptions{
|
||||||
|
Cipher: cipher,
|
||||||
|
})
|
||||||
|
require.NoError(t, bootstrap.SaveBootstrapConfig(context.Background(), writer, &src))
|
||||||
|
|
||||||
dst, err := bootstrap.LoadBootstrapConfigFromFile(fd.Name(), cipher)
|
dst, err := bootstrap.LoadBootstrapConfigFromFile(fd.Name(), cipher)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package k8s
|
package k8s
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -21,15 +21,15 @@ import (
|
||||||
"github.com/pomerium/pomerium/internal/zero/bootstrap/writers"
|
"github.com/pomerium/pomerium/internal/zero/bootstrap/writers"
|
||||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||||
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
|
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
writers.RegisterBuilder("secret", func(uri *url.URL) (writers.ConfigWriter, error) {
|
writers.RegisterBuilder("secret", newSecretWriter)
|
||||||
return newSecretWriter(uri)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type secretWriter struct {
|
type secretWriter struct {
|
||||||
|
opts writers.ConfigWriterOptions
|
||||||
client *http.Client
|
client *http.Client
|
||||||
apiserverURL *url.URL
|
apiserverURL *url.URL
|
||||||
namespace string
|
namespace string
|
||||||
|
@ -37,7 +37,14 @@ type secretWriter struct {
|
||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSecretWriter(uri *url.URL) (*secretWriter, error) {
|
// WithOptions implements writers.ConfigWriter.
|
||||||
|
func (w *secretWriter) WithOptions(opts writers.ConfigWriterOptions) writers.ConfigWriter {
|
||||||
|
clone := *w
|
||||||
|
clone.opts = opts
|
||||||
|
return &clone
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSecretWriter(uri *url.URL) (writers.ConfigWriter, error) {
|
||||||
client, apiserverURL, err := inClusterConfig()
|
client, apiserverURL, err := inClusterConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -59,7 +66,7 @@ func newSecretWriter(uri *url.URL) (*secretWriter, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// WriteConfig implements ConfigWriter.
|
// WriteConfig implements ConfigWriter.
|
||||||
func (w *secretWriter) WriteConfig(ctx context.Context, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error {
|
func (w *secretWriter) WriteConfig(ctx context.Context, src *cluster_api.BootstrapConfig) error {
|
||||||
u := w.apiserverURL.ResolveReference(&url.URL{
|
u := w.apiserverURL.ResolveReference(&url.URL{
|
||||||
Path: path.Join("/api/v1/namespaces", w.namespace, "secrets", w.name),
|
Path: path.Join("/api/v1/namespaces", w.namespace, "secrets", w.name),
|
||||||
RawQuery: url.Values{
|
RawQuery: url.Values{
|
||||||
|
@ -67,23 +74,29 @@ func (w *secretWriter) WriteConfig(ctx context.Context, src *cluster_api.Bootstr
|
||||||
"force": {"true"},
|
"force": {"true"},
|
||||||
}.Encode(),
|
}.Encode(),
|
||||||
})
|
})
|
||||||
plaintext, err := json.Marshal(src)
|
data, err := json.Marshal(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ciphertext := cryptutil.Encrypt(cipher, plaintext, nil)
|
|
||||||
encodedCiphertext := base64.StdEncoding.EncodeToString(ciphertext)
|
|
||||||
|
|
||||||
patch := fmt.Sprintf(`---
|
if w.opts.Cipher != nil {
|
||||||
apiVersion: v1
|
data = cryptutil.Encrypt(w.opts.Cipher, data, nil)
|
||||||
kind: Secret
|
}
|
||||||
metadata:
|
encodedData := base64.StdEncoding.EncodeToString(data)
|
||||||
name: %q
|
|
||||||
namespace: %q
|
patch, _ := yaml.Marshal(map[string]any{
|
||||||
data:
|
"apiVersion": "v1",
|
||||||
%q: %q
|
"kind": "Secret",
|
||||||
`, w.name, w.namespace, w.key, encodedCiphertext)
|
"metadata": map[string]any{
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, u.String(), strings.NewReader(patch))
|
"name": w.name,
|
||||||
|
"namespace": w.namespace,
|
||||||
|
},
|
||||||
|
"data": map[string]string{
|
||||||
|
w.key: encodedData,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, u.String(), bytes.NewReader(patch))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -101,11 +114,17 @@ data:
|
||||||
if resp.Header.Get("Content-Type") == "application/json" {
|
if resp.Header.Get("Content-Type") == "application/json" {
|
||||||
// log the detailed status message if available
|
// log the detailed status message if available
|
||||||
status, err := io.ReadAll(resp.Body)
|
status, err := io.ReadAll(resp.Body)
|
||||||
if err != nil && len(status) > 0 {
|
if err != nil {
|
||||||
log.Ctx(ctx).Error().
|
break
|
||||||
RawJSON("response", status).
|
|
||||||
Msg("forbidden")
|
|
||||||
}
|
}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err = json.Compact(&buf, status)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Ctx(ctx).Error().
|
||||||
|
RawJSON("response", buf.Bytes()).
|
||||||
|
Msgf("%s %s: %s", req.Method, req.URL, resp.Status)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Errorf("unexpected status: %s", resp.Status)
|
return fmt.Errorf("unexpected status: %s", resp.Status)
|
||||||
|
|
|
@ -70,7 +70,11 @@ func TestInClusterConfig(t *testing.T) {
|
||||||
DatabrokerStorageConnection: &txt,
|
DatabrokerStorageConnection: &txt,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, bootstrap.SaveBootstrapConfig(context.Background(), writer, &src, cipher))
|
writer := writer.WithOptions(writers.ConfigWriterOptions{
|
||||||
|
Cipher: cipher,
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, bootstrap.SaveBootstrapConfig(context.Background(), writer, &src))
|
||||||
|
|
||||||
r := <-requests
|
r := <-requests
|
||||||
assert.Equal(t, "PATCH", r.Method)
|
assert.Equal(t, "PATCH", r.Method)
|
||||||
|
@ -141,7 +145,6 @@ func TestInClusterConfig(t *testing.T) {
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
for _, uri := range tc.uris {
|
for _, uri := range tc.uris {
|
||||||
|
|
||||||
w, err := writers.NewForURI(uri)
|
w, err := writers.NewForURI(uri)
|
||||||
if tc.errf == "" {
|
if tc.errf == "" {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
|
@ -11,7 +11,14 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConfigWriter interface {
|
type ConfigWriter interface {
|
||||||
WriteConfig(ctx context.Context, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error
|
WriteConfig(ctx context.Context, src *cluster_api.BootstrapConfig) error
|
||||||
|
WithOptions(opts ConfigWriterOptions) ConfigWriter
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigWriterOptions struct {
|
||||||
|
// A cipher used to encrypt the configuration before writing it.
|
||||||
|
// If nil, the configuration will be written in plaintext.
|
||||||
|
Cipher cipher.AEAD
|
||||||
}
|
}
|
||||||
|
|
||||||
// A WriterBuilder creates and initializes a new ConfigWriter previously
|
// A WriterBuilder creates and initializes a new ConfigWriter previously
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue