-
Notifications
You must be signed in to change notification settings - Fork 126
Watch for Secrets (draft) #765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package config | ||
|
||
import ( | ||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/nginx/file" | ||
"github.com/nginxinc/nginx-kubernetes-gateway/internal/state/dataplane" | ||
) | ||
|
||
|
@@ -10,7 +11,7 @@ import ( | |
// This interface is used for testing purposes only. | ||
type Generator interface { | ||
// Generate generates NGINX configuration from internal representation. | ||
Generate(configuration dataplane.Configuration) []byte | ||
Generate(configuration dataplane.Configuration) []file.File | ||
} | ||
|
||
// GeneratorImpl is an implementation of Generator. | ||
|
@@ -28,13 +29,39 @@ type executeFunc func(configuration dataplane.Configuration) []byte | |
// It is the responsibility of the caller to validate the configuration before calling this function. | ||
// In case of invalid configuration, NGINX will fail to reload or could be configured with malicious configuration. | ||
// To validate, use the validators from the validation package. | ||
func (g GeneratorImpl) Generate(conf dataplane.Configuration) []byte { | ||
func (g GeneratorImpl) Generate(conf dataplane.Configuration) []file.File { | ||
files := make([]file.File, 0, len(conf.TLSCerts)+1) | ||
|
||
// certs | ||
for id, cert := range conf.TLSCerts { | ||
contents := make([]byte, 0, len(cert.Cert)+len(cert.Key)+1) | ||
contents = append(contents, cert.Cert...) | ||
contents = append(contents, '\n') | ||
contents = append(contents, cert.Key...) | ||
|
||
files = append(files, file.File{ | ||
Content: contents, | ||
Path: generateTLSCertPath(id), | ||
Permissions: 0600, | ||
}) | ||
} | ||
|
||
var generated []byte | ||
for _, execute := range getExecuteFuncs() { | ||
generated = append(generated, execute(conf)...) | ||
} | ||
|
||
return generated | ||
files = append(files, file.File{ | ||
Content: generated, | ||
Path: "/etc/nginx/conf.d/http.conf", | ||
Permissions: 0644, | ||
}) | ||
|
||
return files | ||
} | ||
|
||
func generateTLSCertPath(id dataplane.TLSCertID) string { | ||
return "/etc/nginx/secrets/" + string(id) + ".pem" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prefix is probably worth a const |
||
} | ||
|
||
func getExecuteFuncs() []executeFunc { | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -3,47 +3,62 @@ package file | |||||
import ( | ||||||
"fmt" | ||||||
"os" | ||||||
"path/filepath" | ||||||
) | ||||||
|
||||||
const confdFolder = "/etc/nginx/conf.d" | ||||||
|
||||||
//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 . Manager | ||||||
|
||||||
type File struct { | ||||||
Path string | ||||||
Content []byte | ||||||
Permissions os.FileMode | ||||||
} | ||||||
|
||||||
// Manager manages NGINX configuration files. | ||||||
type Manager interface { | ||||||
// WriteHTTPConfig writes the http config on the file system. | ||||||
// The name distinguishes this config among all other configs. For that, it must be unique. | ||||||
// Note that name is not the name of the corresponding configuration file. | ||||||
WriteHTTPConfig(name string, cfg []byte) error | ||||||
// ReplaceFiles replaces the files on the file system with the given files. | ||||||
ReplaceFiles(files []File) error | ||||||
} | ||||||
|
||||||
// ManagerImpl is an implementation of Manager. | ||||||
type ManagerImpl struct{} | ||||||
type ManagerImpl struct { | ||||||
lastWrittenPaths []string | ||||||
} | ||||||
|
||||||
// NewManagerImpl creates a new NewManagerImpl. | ||||||
func NewManagerImpl() *ManagerImpl { | ||||||
return &ManagerImpl{} | ||||||
} | ||||||
|
||||||
func (m *ManagerImpl) WriteHTTPConfig(name string, cfg []byte) error { | ||||||
path := getPathForConfig(name) | ||||||
|
||||||
file, err := os.Create(path) | ||||||
if err != nil { | ||||||
return fmt.Errorf("failed to create server config %s: %w", path, err) | ||||||
func (m *ManagerImpl) ReplaceFiles(files []File) error { | ||||||
for _, path := range m.lastWrittenPaths { | ||||||
err := os.Remove(path) | ||||||
if err != nil { | ||||||
return fmt.Errorf("failed to delete file %s: %w", path, err) | ||||||
} | ||||||
} | ||||||
|
||||||
defer file.Close() | ||||||
// In some cases, NGINX reads files in runtime, like JWT secrets | ||||||
// In that case, removal will lead to errors. | ||||||
// However, we don't have such files yet. so not a problem | ||||||
|
||||||
m.lastWrittenPaths = make([]string, 0, len(files)) | ||||||
|
||||||
_, err = file.Write(cfg) | ||||||
if err != nil { | ||||||
return fmt.Errorf("failed to write server config %s: %w", path, err) | ||||||
for _, file := range files { | ||||||
f, err := os.Create(file.Path) | ||||||
if err != nil { | ||||||
return fmt.Errorf("failed to create server config %s: %w", file.Path, err) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
defer f.Close() | ||||||
|
||||||
_, err = f.Write(file.Content) | ||||||
if err != nil { | ||||||
return fmt.Errorf("failed to write server config %s: %w", file.Path, err) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
m.lastWrittenPaths = append(m.lastWrittenPaths, file.Path) | ||||||
} | ||||||
|
||||||
return nil | ||||||
} | ||||||
|
||||||
func getPathForConfig(name string) string { | ||||||
return filepath.Join(confdFolder, name+".conf") | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's either make this a const, or what if we updated our dataplane.Configuration to have an
http
section, and in the future we'd add astream
section, where each could contain their respective configs and file paths? That's probably out of scope for this, but maybe worth a ticket?