Write certificate file and set privileges

This commit is contained in:
Emmanuel BENOîT 2021-11-05 17:16:44 +01:00
parent 4619b592e6
commit f95da0e3e8
2 changed files with 151 additions and 5 deletions

View file

@ -4,18 +4,35 @@ import (
"encoding/pem" "encoding/pem"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"os/user"
"strconv"
"syscall"
"github.com/sirupsen/logrus"
) )
// Max supported CA chain length // Max supported CA chain length
const MAX_CA_CHAIN_LENGTH = 8 const MAX_CA_CHAIN_LENGTH = 8
type ( type (
// Structure that describes the existing file for a certificate.
tExistingFileInfo struct {
owner uint32
group uint32
mode os.FileMode
}
// Certificate building, including the configuration, LDAP connection, // Certificate building, including the configuration, LDAP connection,
// and the array of chunks that's being built. // and the array of chunks that's being built.
tCertificateBuilder struct { tCertificateBuilder struct {
config *tCertificateFileConfig config *tCertificateFileConfig
conn *tLdapConn conn *tLdapConn
logger *logrus.Entry
data [][]byte data [][]byte
text []byte
existing *tExistingFileInfo
changed bool
} }
) )
@ -25,6 +42,7 @@ func NewCertificateBuilder(conn *tLdapConn, config *tCertificateFileConfig) tCer
return tCertificateBuilder{ return tCertificateBuilder{
config: config, config: config,
conn: conn, conn: conn,
logger: log.WithField("file", config.Path),
data: make([][]byte, 0), data: make([][]byte, 0),
} }
} }
@ -32,6 +50,7 @@ func NewCertificateBuilder(conn *tLdapConn, config *tCertificateFileConfig) tCer
// Build the certificate file's data, returning any error that occurs while // Build the certificate file's data, returning any error that occurs while
// reading the source data. // reading the source data.
func (b *tCertificateBuilder) Build() error { func (b *tCertificateBuilder) Build() error {
b.logger.Debug("Checking for updates")
err := b.appendPemFiles(b.config.PrependFiles) err := b.appendPemFiles(b.config.PrependFiles)
if err != nil { if err != nil {
return err return err
@ -51,13 +70,102 @@ func (b *tCertificateBuilder) Build() error {
if b.config.Reverse { if b.config.Reverse {
b.reverseChunks() b.reverseChunks()
} }
b.generateText()
return nil return nil
} }
// Check whether the data should be written to disk.
func (b *tCertificateBuilder) MustWrite() bool {
info, err := os.Lstat(b.config.Path)
if err != nil {
return true
}
sys_stat := info.Sys().(*syscall.Stat_t)
eif := &tExistingFileInfo{}
eif.mode = info.Mode()
eif.owner = sys_stat.Uid
eif.group = sys_stat.Gid
b.existing = eif
if sys_stat.Size != int64(len(b.text)) {
return true
}
existing, err := ioutil.ReadFile(b.config.Path)
if err != nil {
return true
}
for i, ch := range b.text {
if ch != existing[i] {
return true
}
}
return false
}
// Write the file's data
func (b *tCertificateBuilder) WriteFile() error {
log.WithField("file", b.config.Path).Info("Writing certificate data to file")
err := ioutil.WriteFile(b.config.Path, b.text, b.config.Mode)
if err == nil {
b.changed = true
}
return err
}
// Update the file's owner and group
func (b *tCertificateBuilder) UpdatePrivileges() error {
update_mode := !b.changed && b.existing.mode != b.config.Mode
if update_mode {
err := os.Chmod(b.config.Path, b.config.Mode)
if err != nil {
return err
}
}
log := b.logger
set_uid, set_gid := -1, -1
if b.config.Owner != "" {
usr, err := user.Lookup(b.config.Owner)
if err != nil {
return err
}
uid, err := strconv.Atoi(usr.Uid)
if b.changed || b.existing == nil || b.existing.owner != uint32(uid) {
set_uid = uid
log = log.WithField("uid", set_uid)
}
}
if b.config.Group != "" {
group, err := user.LookupGroup(b.config.Group)
if err != nil {
return err
}
gid, err := strconv.Atoi(group.Gid)
if b.changed || b.existing == nil || b.existing.group != uint32(gid) {
set_gid = gid
log = log.WithField("gid", set_gid)
}
}
if set_gid != -1 || set_uid != -1 {
log.Info("Updating file owner/group")
err := os.Chown(b.config.Path, set_uid, set_gid)
if err == nil {
b.changed = true
}
return err
} else {
b.changed = b.changed || update_mode
log.Debug("No update to privileges")
return nil
}
}
// Append PEM files from a list. // Append PEM files from a list.
func (b *tCertificateBuilder) appendPemFiles(files []string) error { func (b *tCertificateBuilder) appendPemFiles(files []string) error {
for _, path := range files { for _, path := range files {
var err error var err error
b.logger.WithField("source", path).Debug("Adding PEM file")
err = b.appendPem(path) err = b.appendPem(path)
if err != nil { if err != nil {
return err return err
@ -98,6 +206,7 @@ func (b *tCertificateBuilder) appendCertificate() error {
dn = "," + dn dn = "," + dn
} }
dn = b.config.Certificate + dn dn = b.config.Certificate + dn
b.logger.WithField("dn", dn).Debug("Adding EE certificate from LDAP")
data, err := b.conn.getEndEntityCertificate(dn) data, err := b.conn.getEndEntityCertificate(dn)
if err != nil { if err != nil {
return err return err
@ -126,6 +235,7 @@ func (b *tCertificateBuilder) appendListedCaCerts() error {
bdn = "," + bdn bdn = "," + bdn
} }
for _, dn := range b.config.CACertificates { for _, dn := range b.config.CACertificates {
b.logger.WithField("dn", dn+bdn).Debug("Adding CA certificate from LDAP")
data, _, err := b.conn.getCaCertificate(dn + bdn) data, _, err := b.conn.getCaCertificate(dn + bdn)
if err != nil { if err != nil {
return err return err
@ -154,6 +264,7 @@ func (b *tCertificateBuilder) appendChainedCaCerts() error {
if data == nil { if data == nil {
return fmt.Errorf("No CA certificate at DN '%s'", dn) return fmt.Errorf("No CA certificate at DN '%s'", dn)
} }
b.logger.WithField("dn", dn).Debug("Adding CA certificate from LDAP chain")
b.data = append(b.data, data) b.data = append(b.data, data)
} }
if nextDn == "" { if nextDn == "" {
@ -169,9 +280,32 @@ func (b *tCertificateBuilder) appendChainedCaCerts() error {
// Reverse the chunks in the list // Reverse the chunks in the list
func (b *tCertificateBuilder) reverseChunks() { func (b *tCertificateBuilder) reverseChunks() {
b.logger.Debug("Reversing PEM list")
l := len(b.data) / 2 l := len(b.data) / 2
for i := 0; i < l/2; i++ { for i := 0; i < l/2; i++ {
j := l - i - 1 j := l - i - 1
b.data[i], b.data[j] = b.data[j], b.data[i] b.data[i], b.data[j] = b.data[j], b.data[i]
} }
} }
// Generate the final text of the file
func (b *tCertificateBuilder) generateText() {
size := int64(0)
for i := range b.data {
size += int64(len(b.data[i]))
if i != 0 && b.data[i-1][len(b.data[i-1])-1] != '\n' {
size++
}
}
b.text = make([]byte, size)
pos := 0
for i := range b.data {
copied := copy(b.text[pos:], b.data[i])
pos += copied
if i != 0 && b.data[i-1][len(b.data[i-1])-1] != '\n' {
b.text[pos] = '\n'
pos++
}
}
b.logger.WithField("size", size).Debug("Data generated")
}

16
main.go
View file

@ -80,9 +80,21 @@ func main() {
builder := NewCertificateBuilder(conn, &cfg.Certificates[i]) builder := NewCertificateBuilder(conn, &cfg.Certificates[i])
err := builder.Build() err := builder.Build()
if err != nil { if err != nil {
log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path) log.WithField("error", err).Error("Failed to build data for certificate '", cfg.Certificates[i].Path, "'")
continue continue
} }
// FIXME: check existing file, try to write if builder.MustWrite() {
err := builder.WriteFile()
if err != nil {
log.WithField("error", err).Error("Failed to write '", cfg.Certificates[i].Path, "'")
continue
}
}
err = builder.UpdatePrivileges()
if err != nil {
log.WithField("error", err).Error("Failed to update privileges on '", cfg.Certificates[i].Path, "'")
continue
}
// TODO builder.RunCommandsIfChanged()
} }
} }