Configuration validation
* Validate most of the entries in the configuration file, with the exception of DNs / RDNs. * Use the govalidator library for DNS names and file paths
This commit is contained in:
parent
e6aaa09795
commit
f971c1e961
2 changed files with 201 additions and 5 deletions
205
config.go
205
config.go
|
@ -1,10 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/user"
|
||||
"strconv"
|
||||
|
||||
valid "github.com/asaskevich/govalidator"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
|
@ -78,27 +82,218 @@ type (
|
|||
}
|
||||
)
|
||||
|
||||
// Helper function that checks whether a string corresponds to a group name.
|
||||
func isValidGroup(name string) bool {
|
||||
group, err := user.LookupGroup(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = strconv.Atoi(group.Gid)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Helper function that checks whether a string corresponds to a user name.
|
||||
func isValidUser(name string) bool {
|
||||
user, err := user.Lookup(name)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = strconv.Atoi(user.Uid)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Validate the UNIX socket configuration
|
||||
func (c *tSocketConfig) Validate() error {
|
||||
if c.Path == "" {
|
||||
return fmt.Errorf("Missing socket path.")
|
||||
}
|
||||
if !valid.IsUnixFilePath(c.Path) {
|
||||
return fmt.Errorf("Socket path '%s' is invalid.", c.Path)
|
||||
}
|
||||
if c.Group != "" && !isValidGroup(c.Group) {
|
||||
return fmt.Errorf("Invalid group '%s'", c.Group)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the LDAP structure configuration.
|
||||
func (c *tLdapStructureConfig) Validate() error {
|
||||
if c.EndEntityCertificate == "" {
|
||||
return fmt.Errorf("Missing end entity certificate attribute name.")
|
||||
}
|
||||
if c.CACertificate == "" {
|
||||
return fmt.Errorf("Missing CA certificate attribute name.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check the TLS field in LDAP configuration entries. If no port is specified,
|
||||
// default it based on the TLS field.
|
||||
func (c *tLdapConnectionConfig) Validate() error {
|
||||
if c.TLS != "yes" && c.TLS != "starttls" && c.TLS != "no" {
|
||||
return fmt.Errorf("Invalid TLS mode '%s' (valid values are 'yes', 'starttls' and 'no'", c.TLS)
|
||||
}
|
||||
if c.CaChain != "" {
|
||||
data, err := ioutil.ReadFile(c.CaChain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read CA chain from '%s': %w", c.CaChain, err)
|
||||
}
|
||||
pool := x509.NewCertPool()
|
||||
if !pool.AppendCertsFromPEM(data) {
|
||||
return fmt.Errorf("Could not parse CA chain PEM from '%s'.", c.CaChain)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Copy defaults into a LDAP server configuration entry.
|
||||
func (c *tLdapServerConfig) ApplyDefaults(dft tLdapConnectionConfig) {
|
||||
if c.Port == 0 {
|
||||
c.Port = dft.Port
|
||||
}
|
||||
if c.TLS == "" {
|
||||
c.TLS = dft.TLS
|
||||
}
|
||||
// FIXME: I have no clue how I should handle TLSNoVerify
|
||||
if c.CaChain == "" {
|
||||
c.CaChain = dft.CaChain
|
||||
}
|
||||
if c.BindUser == "" {
|
||||
c.BindUser = dft.BindUser
|
||||
}
|
||||
if c.BindPassword == "" {
|
||||
c.BindPassword = dft.BindPassword
|
||||
}
|
||||
|
||||
// Default port based on TLS mode
|
||||
if c.Port == 0 {
|
||||
if c.TLS == "starttls" {
|
||||
c.Port = 636
|
||||
} else {
|
||||
c.Port = 389
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate a LDAP server configuration record.
|
||||
func (c *tLdapServerConfig) Validate() error {
|
||||
if c.Host == "" {
|
||||
return fmt.Errorf("No host name in LDAP server configuration.")
|
||||
}
|
||||
if !valid.IsHost(c.Host) {
|
||||
return fmt.Errorf("Invalid host name '%s'", c.Host)
|
||||
}
|
||||
return c.tLdapConnectionConfig.Validate()
|
||||
}
|
||||
|
||||
// Validate the LDAP configuration
|
||||
func (c *tLdapConfig) Validate() error {
|
||||
err := c.Structure.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.Defaults.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(c.Servers) == 0 {
|
||||
return fmt.Errorf("No LDAP servers have been configured.")
|
||||
}
|
||||
for i := range c.Servers {
|
||||
c.Servers[i].ApplyDefaults(c.Defaults)
|
||||
err = c.Servers[i].Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check that a list of files contains only valid paths
|
||||
func checkFileList(files []string) error {
|
||||
for _, path := range files {
|
||||
if !valid.IsUnixFilePath(path) {
|
||||
return fmt.Errorf("Invalid path '%s'", path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate a certificate file configuration entry
|
||||
func (c *tCertificateFileConfig) Validate() error {
|
||||
if c.Path == "" {
|
||||
return fmt.Errorf("Certificate file entry has no path.")
|
||||
}
|
||||
if !valid.IsUnixFilePath(c.Path) {
|
||||
return fmt.Errorf("Certificate file path '%s' is invalid.", c.Path)
|
||||
}
|
||||
if c.Owner != "" && !isValidUser(c.Owner) {
|
||||
return fmt.Errorf("Unknown user '%s'", c.Owner)
|
||||
}
|
||||
if c.Group != "" && !isValidGroup(c.Group) {
|
||||
return fmt.Errorf("Invalid group '%s'", c.Group)
|
||||
}
|
||||
err := checkFileList(c.PrependFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, path := range c.PrependFiles {
|
||||
if !valid.IsUnixFilePath(path) {
|
||||
return fmt.Errorf("Invalid path '%s'", path)
|
||||
}
|
||||
}
|
||||
if c.Certificate == "" && len(c.CACertificates) == 0 && c.CAChainOf == "" {
|
||||
return fmt.Errorf("Certificate path '%s' has no certificate or CA chain", c.Path)
|
||||
}
|
||||
if c.CAChainOf != "" && len(c.CACertificates) != 0 {
|
||||
return fmt.Errorf("Certificate path '%s' uses both 'ca' and 'ca_chain_of'", c.Path)
|
||||
}
|
||||
err = checkFileList(c.AppendFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Validate the configuration
|
||||
func (c *tConfiguration) Validate() error {
|
||||
err := c.Socket.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = c.LdapConfig.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, cfc := range c.Certificates {
|
||||
err = cfc.Validate()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a configuration data structure containing default values.
|
||||
func defaultConfiguration() tConfiguration {
|
||||
cfg := tConfiguration{}
|
||||
cfg.Socket.Mode = 0640
|
||||
cfg.LdapConfig.Defaults.Port = 389
|
||||
cfg.LdapConfig.Defaults.TLS = "no"
|
||||
cfg.LdapConfig.Structure.CAChaining = "seeAlso"
|
||||
return cfg
|
||||
}
|
||||
|
||||
// Load and check the configuration file
|
||||
func loadConfiguration(file string) (tConfiguration, error) {
|
||||
cfg := defaultConfiguration()
|
||||
|
||||
cfgData, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("Could not load configuration: %w", err)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(cfgData, &cfg)
|
||||
if err != nil {
|
||||
return cfg, fmt.Errorf("Could not parse configuration: %w", err)
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
err = cfg.Validate()
|
||||
return cfg, err
|
||||
}
|
||||
|
|
1
go.mod
1
go.mod
|
@ -3,6 +3,7 @@ module nocternity.net/go/fetchcert
|
|||
go 1.17
|
||||
|
||||
require (
|
||||
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
|
||||
github.com/gemnasium/logrus-graylog-hook/v3 v3.0.3
|
||||
github.com/karrick/golf v1.4.0
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
|
|
Loading…
Reference in a new issue