mirror of
https://github.com/pomerium/pomerium.git
synced 2025-08-01 07:50:26 +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
|
||||
}
|
||||
|
||||
err = SaveBootstrapConfig(ctx, svc.writer, cfg, svc.fileCipher)
|
||||
err = SaveBootstrapConfig(ctx, svc.writer, cfg)
|
||||
if err != nil {
|
||||
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")
|
||||
|
|
|
@ -42,8 +42,8 @@ func LoadBootstrapConfigFromFile(fp string, cipher cipher.AEAD) (*cluster_api.Bo
|
|||
}
|
||||
|
||||
// SaveBootstrapConfig saves the bootstrap configuration to a file.
|
||||
func SaveBootstrapConfig(ctx context.Context, writer writers.ConfigWriter, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error {
|
||||
err := writer.WriteConfig(ctx, src, cipher)
|
||||
func SaveBootstrapConfig(ctx context.Context, writer writers.ConfigWriter, src *cluster_api.BootstrapConfig) error {
|
||||
err := writer.WriteConfig(ctx, src)
|
||||
if err != nil {
|
||||
health.ReportError(health.ZeroBootstrapConfigSave, err)
|
||||
} else {
|
||||
|
|
|
@ -55,6 +55,12 @@ func New(secret []byte, fileCachePath *string, writer writers.ConfigWriter, api
|
|||
return nil, fmt.Errorf("init cypher: %w", err)
|
||||
}
|
||||
|
||||
if writer != nil {
|
||||
writer = writer.WithOptions(writers.ConfigWriterOptions{
|
||||
Cipher: cipher,
|
||||
})
|
||||
}
|
||||
|
||||
svc := &Source{
|
||||
api: api,
|
||||
source: source{ready: make(chan struct{})},
|
||||
|
|
|
@ -2,7 +2,6 @@ package filesystem
|
|||
|
||||
import (
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
@ -14,30 +13,42 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
writers.RegisterBuilder("file", func(uri *url.URL) (writers.ConfigWriter, error) {
|
||||
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)
|
||||
}
|
||||
return &fileWriter{
|
||||
filePath: uri.Path,
|
||||
}, nil
|
||||
})
|
||||
writers.RegisterBuilder("file", newFileWriter)
|
||||
}
|
||||
|
||||
func newFileWriter(uri *url.URL) (writers.ConfigWriter, error) {
|
||||
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)
|
||||
}
|
||||
return &fileWriter{
|
||||
filePath: uri.Path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type fileWriter struct {
|
||||
opts writers.ConfigWriterOptions
|
||||
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.
|
||||
func (w *fileWriter) WriteConfig(_ context.Context, src *cluster_api.BootstrapConfig, cipher cipher.AEAD) error {
|
||||
plaintext, err := json.Marshal(src)
|
||||
func (w *fileWriter) WriteConfig(_ context.Context, src *cluster_api.BootstrapConfig) error {
|
||||
data, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal file config: %w", err)
|
||||
}
|
||||
|
||||
ciphertext := cryptutil.Encrypt(cipher, plaintext, nil)
|
||||
err = os.WriteFile(w.filePath, ciphertext, 0o600)
|
||||
if w.opts.Cipher != nil {
|
||||
data = cryptutil.Encrypt(w.opts.Cipher, data, nil)
|
||||
}
|
||||
err = os.WriteFile(w.filePath, data, 0o600)
|
||||
if err != nil {
|
||||
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()))
|
||||
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)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package k8s
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/cipher"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
|
@ -21,15 +21,15 @@ import (
|
|||
"github.com/pomerium/pomerium/internal/zero/bootstrap/writers"
|
||||
"github.com/pomerium/pomerium/pkg/cryptutil"
|
||||
cluster_api "github.com/pomerium/pomerium/pkg/zero/cluster"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
writers.RegisterBuilder("secret", func(uri *url.URL) (writers.ConfigWriter, error) {
|
||||
return newSecretWriter(uri)
|
||||
})
|
||||
writers.RegisterBuilder("secret", newSecretWriter)
|
||||
}
|
||||
|
||||
type secretWriter struct {
|
||||
opts writers.ConfigWriterOptions
|
||||
client *http.Client
|
||||
apiserverURL *url.URL
|
||||
namespace string
|
||||
|
@ -37,7 +37,14 @@ type secretWriter struct {
|
|||
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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -59,7 +66,7 @@ func newSecretWriter(uri *url.URL) (*secretWriter, error) {
|
|||
}
|
||||
|
||||
// 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{
|
||||
Path: path.Join("/api/v1/namespaces", w.namespace, "secrets", w.name),
|
||||
RawQuery: url.Values{
|
||||
|
@ -67,23 +74,29 @@ func (w *secretWriter) WriteConfig(ctx context.Context, src *cluster_api.Bootstr
|
|||
"force": {"true"},
|
||||
}.Encode(),
|
||||
})
|
||||
plaintext, err := json.Marshal(src)
|
||||
data, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ciphertext := cryptutil.Encrypt(cipher, plaintext, nil)
|
||||
encodedCiphertext := base64.StdEncoding.EncodeToString(ciphertext)
|
||||
|
||||
patch := fmt.Sprintf(`---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: %q
|
||||
namespace: %q
|
||||
data:
|
||||
%q: %q
|
||||
`, w.name, w.namespace, w.key, encodedCiphertext)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPatch, u.String(), strings.NewReader(patch))
|
||||
if w.opts.Cipher != nil {
|
||||
data = cryptutil.Encrypt(w.opts.Cipher, data, nil)
|
||||
}
|
||||
encodedData := base64.StdEncoding.EncodeToString(data)
|
||||
|
||||
patch, _ := yaml.Marshal(map[string]any{
|
||||
"apiVersion": "v1",
|
||||
"kind": "Secret",
|
||||
"metadata": map[string]any{
|
||||
"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 {
|
||||
return err
|
||||
}
|
||||
|
@ -101,11 +114,17 @@ data:
|
|||
if resp.Header.Get("Content-Type") == "application/json" {
|
||||
// log the detailed status message if available
|
||||
status, err := io.ReadAll(resp.Body)
|
||||
if err != nil && len(status) > 0 {
|
||||
log.Ctx(ctx).Error().
|
||||
RawJSON("response", status).
|
||||
Msg("forbidden")
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
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)
|
||||
|
|
|
@ -70,7 +70,11 @@ func TestInClusterConfig(t *testing.T) {
|
|||
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
|
||||
assert.Equal(t, "PATCH", r.Method)
|
||||
|
@ -141,7 +145,6 @@ func TestInClusterConfig(t *testing.T) {
|
|||
},
|
||||
} {
|
||||
for _, uri := range tc.uris {
|
||||
|
||||
w, err := writers.NewForURI(uri)
|
||||
if tc.errf == "" {
|
||||
assert.NoError(t, err)
|
||||
|
|
|
@ -11,7 +11,14 @@ import (
|
|||
)
|
||||
|
||||
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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue