Server socket

* The UNIX socket will be listened on for control messages
  * A message containing a single 'Q' will cause it to exit
  * A message containing a single 'R' will cause a configuration reload.
    If the new configuration is incorrect, the old configuration will be
    kept. A new socket will be opened if the path has changed (failure
    when doing so will restore the previous configuration as well).
  * A message starting with 'U' requests an update. The next character
    may be '!' to force updates or anything else to update only as
    needed. The rest of the string is the selector: either a DN or '*'.
  * The selector is ignored in this commit; all certificates are
    re-examined.
This commit is contained in:
Emmanuel BENOîT 2021-11-06 17:12:08 +01:00
parent 50bff6a5cb
commit 658ee30bc6
3 changed files with 161 additions and 26 deletions

View file

@ -284,7 +284,7 @@ func defaultConfiguration() tConfiguration {
}
// Load and check the configuration file
func loadConfiguration(file string) (tConfiguration, error) {
func LoadConfiguration(file string) (tConfiguration, error) {
cfg := defaultConfiguration()
cfgData, err := ioutil.ReadFile(file)
if err != nil {

50
main.go
View file

@ -60,7 +60,7 @@ func main() {
log.WithField("error", err).Fatal("Failed to configure logging.")
}
cfg, err := loadConfiguration(flags.cfgFile)
cfg, err := LoadConfiguration(flags.cfgFile)
if err != nil {
log.WithField("error", err).Fatal("Failed to load initial configuration.")
}
@ -69,32 +69,32 @@ func main() {
if err != nil {
log.WithField("error", err).Fatal("Failed to initialize socket.")
}
defer listener.Close()
for {
cmd := socketServer(&cfg, listener)
if cmd == CMD_QUIT {
break
} else if cmd == CMD_RELOAD {
new_cfg, err := LoadConfiguration(flags.cfgFile)
if err != nil {
log.WithField("error", err).Error("Failed to load updated configuration.")
} else {
replace_ok := true
if new_cfg.Socket.Path != cfg.Socket.Path {
new_listener, err := initSocket(new_cfg.Socket)
if err != nil {
log.WithField("error", err).Error("Failed to initialize new server socket.")
replace_ok = false
} else {
listener.Close()
conn := NewLdapConnection(cfg.LdapConfig)
if conn == nil {
return
}
defer conn.Close()
for i := range cfg.Certificates {
builder := NewCertificateBuilder(conn, &cfg.Certificates[i])
err := builder.Build()
if err != nil {
log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path, "'")
continue
}
if builder.MustWrite() {
err := builder.WriteFile()
if err != nil {
log.WithField("error", err).Error("Failed to write '", cfg.Certificates[i].Path, "'")
continue
listener = new_listener
}
}
if replace_ok {
cfg = new_cfg
log.Info("Configuration reloaded")
}
}
}
err = builder.UpdatePrivileges()
if err != nil {
log.WithField("error", err).Error("Failed to update privileges on '", cfg.Certificates[i].Path, "'")
continue
}
builder.RunCommandsIfChanged()
}
}

135
socket.go
View file

@ -6,8 +6,27 @@ import (
"os"
"os/user"
"strconv"
"time"
"unicode/utf8"
"github.com/sirupsen/logrus"
)
type TCommandType int
const (
CMD_IGNORE TCommandType = iota
CMD_QUIT
CMD_RELOAD
CMD_UPDATE
)
type TCommand struct {
CommandType TCommandType
Force bool
Selector string
}
func configureSocket(cfg tSocketConfig) error {
if cfg.Group != "" {
group, err := user.LookupGroup(cfg.Group)
@ -47,3 +66,119 @@ func initSocket(cfg tSocketConfig) (net.Listener, error) {
log.WithField("path", cfg.Path).Info("UNIX socket created")
return listener, nil
}
func socketServer(cfg *tConfiguration, listener net.Listener) TCommandType {
for {
fd, err := listener.Accept()
if err != nil {
log.WithField("error", err).Fatal("Error while waiting for connections.")
}
cmd := executeFromSocket(cfg, fd)
if cmd != CMD_IGNORE {
return cmd
}
}
}
func executeFromSocket(cfg *tConfiguration, conn net.Conn) TCommandType {
defer conn.Close()
log.Debug("Received connection")
buf := make([]byte, 512)
conn.SetReadDeadline(time.Now().Add(1 * time.Second))
n, err := conn.Read(buf)
if err != nil {
log.WithField("error", err).Error("Could not read from socket")
return CMD_IGNORE
}
command := parseCommand(n, buf)
if command == nil {
return CMD_IGNORE
}
if command.CommandType == CMD_UPDATE {
log.WithFields(logrus.Fields{
"force": command.Force,
"selector": command.Selector,
}).Info("Update request received")
success := executeUpdate(cfg, command)
conn.SetWriteDeadline(time.Now().Add(1 * time.Second))
var bval byte
if success {
bval = '1'
} else {
bval = '0'
}
conn.Write([]byte{bval})
return CMD_IGNORE
}
return command.CommandType
}
func parseCommand(n int, buf []byte) *TCommand {
if n == 512 {
log.Warn("Too much data received")
return nil
}
if n == 0 {
log.Warn("Not enough data received")
return nil
}
if n == 1 {
if buf[0] == 'Q' {
return &TCommand{CommandType: CMD_QUIT}
} else if buf[0] == 'R' {
return &TCommand{CommandType: CMD_RELOAD}
}
} else if n > 2 && buf[0] == 'U' {
res := &TCommand{CommandType: CMD_UPDATE}
if buf[1] == '!' {
res.Force = true
}
if utf8.Valid(buf[2:]) {
res.Selector = string(buf[2:n])
return res
}
}
log.Warn("Invalid command received")
return nil
}
func executeUpdate(cfg *tConfiguration, cmd *TCommand) bool {
conn := NewLdapConnection(cfg.LdapConfig)
if conn == nil {
return false
}
defer conn.Close()
had_errors := false
for i := range cfg.Certificates {
// TODO apply selector
builder := NewCertificateBuilder(conn, &cfg.Certificates[i])
err := builder.Build()
if err != nil {
log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path, "'")
had_errors = true
continue
}
if builder.MustWrite() || cmd.Force {
err := builder.WriteFile()
if err != nil {
log.WithField("error", err).Error("Failed to write '", cfg.Certificates[i].Path, "'")
had_errors = true
continue
}
}
err = builder.UpdatePrivileges()
if err != nil {
log.WithField("error", err).Error("Failed to update privileges on '", cfg.Certificates[i].Path, "'")
had_errors = true
continue
}
err = builder.RunCommandsIfChanged()
if err != nil {
log.WithField("error", err).Error("Failed to run commands after update of '", cfg.Certificates[i].Path, "'")
had_errors = true
}
}
return !had_errors
}