checking in some config stuff

This commit is contained in:
brent s. 2020-10-07 11:25:56 -04:00
parent 4b912a8dae
commit d3a5f039c1
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 349 additions and 67 deletions

1
config/TODO Normal file
View File

@ -0,0 +1 @@
add default config as struct

View File

@ -38,17 +38,20 @@ const (
var (
// sshdModify are values we modify.
sshdModify = [...]string{
"AuthenticationMethods",
"ChallengeResponseAuthentication",
"Ciphers",
"HostKey",
"PermitRootLogin",
"StrictModes",
"PubkeyAuthentication",
"HostKeyAlgorithms",
"KexAlgorithms",
"MACs",
"PasswordAuthentication",
"PermitEmptyPasswords",
"ChallengeResponseAuthentication",
"KexAlgorithms",
"PermitRootLogin",
"PrintLastLog",
"Protocol",
"Ciphers",
"MACs",
"PubkeyAuthentication",
"StrictModes",
}

// sshModify are values we modify.
@ -62,6 +65,10 @@ var (
"",
}

sshMulti = [...]string{
"IdentityFile",
}

// authMethods are authentication methods that openssh supports.
authMethods = []string{
"any",
@ -92,11 +99,13 @@ var (
// forwardAllows are shared values used by forwarding access control.
forwardAllows = []string{"yes", "all", "no", "local", "remote"}

// hostkeyTypes are algorithms/types used for host keys.
// keyTypes are algorithms/types used for host keys and client pubkeys.
// The following should generate the same list.
// ssh -Q HostKeyAlgorithms | sed -re 's/^/"/g' -e 's/$/",/g'
// ssh -Q HostbasedAcceptedKeyTypes | sed -re 's/^/"/g' -e 's/$/",/g'
hostkeyTypes = []string{
// ssh -Q PubkeyAcceptedKeyTypes | sed -re 's/^/"/g' -e 's/$/",/g'
// ssh -Q HostbasedKeyTypes | sed -re 's/^/"/g' -e 's/$/",/g'
keyTypes = []string{
"ssh-ed25519",
"ssh-ed25519-cert-v01@openssh.com",
"sk-ssh-ed25519@openssh.com",
@ -196,6 +205,21 @@ var (
"DEBUG3",
}

// logFacilities is a list of valid syslog facilities.
logFacilities = []string{
"DAEMON",
"USER",
"AUTH", // default
"LOCAL0",
"LOCAL1",
"LOCAL2",
"LOCAL3",
"LOCAL4",
"LOCAL5",
"LOCAL6",
"LOCAL7",
}

// macAlgos is a list of valid MAC (Message Authentication Code) values. "-etm" algos are recommended by upstream.
// ssh -Q mac | sed -re 's/^/"/g' -e 's/$/",/g'
macAlgos = []string{
@ -224,6 +248,7 @@ var (
// Valid keys for sshdMatchCriteria are tracked via field names in SshdMatchRule.
// Multiple criteria can be specified by e.g. "Match User foo, Host bar.tld"
sshdMatchCriteria = []string{
"Address",
"All",
"User",
"Group",
@ -231,18 +256,37 @@ var (
"LocalAddress",
"LocalPort",
"RDomain",
"Address",
}

// sshMatchCriteria is a list of valid criteria that can be used in a Match or Host block.
// Valid keys for sshMatchCriteria are tracked via field names in SshMatchRule.
// Multiple criteria can be specified by e.g. "Match User foo, Host bar.tld"
sshMatchCriteria = []string{
"all", // Must appear alone, or immediately after canonical/final.
"canonical",
"exec", // Requires second argument.
"final",
"host", // Requires second argument.
"localuser", // Requires second argument.
"originalhost", // Requires second argument.
"user", // Requires second argument.
}
)

// The following are validator maps.
var (
// These directives can also begin with "+", "-", or "^", so they need to be stripped off.
// TODO: How to do this non-destructively?
// TODO: How to do this non-destructively and cleanly?
// Currently we just save the struct field's first item with the prefix and strip it in a local var for validating.
sshdStripPre = []string{
"HostbasedAcceptedKeyTypes",
"KexAlgorithms",
}
sshStripPre = []string{
"HostbasedKeyTypes",
"KexAlgorithms",
}

// validSshdSingleVals are values that can accept a single string value from a static list.
validSshdSingleVals = map[string][]string{
"AddressFamily": {"any", "inet", "inet6"},
@ -251,8 +295,13 @@ var (
"Compression": {"yes", "delayed", "no"}, // "delayed" is legacy, same as "yes".
"FingerprintHash": {"sha256", "md5"},
"GatewayPorts": {"yes", "no", "clientspecified"},
"HostbasedAcceptedKeyTypes": keyTypes,
"IgnoreRHosts": {"yes", "shosts-only", "no"},
"LogLevel": logLevels,
"PermitRootLogin": {"yes", "prohibit-password", "forced-commands-only", "no"}, // "without-password" is deprecated.
"PermitTunnel": {"yes", "point-to-point", "ethernet", "no"},
"PubkeyAuthOptions": {"none", "touch-required"},
"SyslogFacility": logFacilities,
}

// validSshdMultiVals are values that can accept multiple values from a static list.
@ -260,9 +309,28 @@ var (
"AuthenticationMethods": authMethods,
"CASignatureAlgorithms": sigAlgos,
"Ciphers": ciphers,
"HostbasedAcceptedKeyTypes": hostkeyTypes, // NOTE: Can also begin with "+", "-", or "^"
"HostKeyAlgorithms": hostkeyTypes,
"KexAlgorithms": kexAlgos, // NOTE: Can also begin with "+", "-", or "^"
"HostbasedAcceptedKeyTypes": keyTypes,
"HostKeyAlgorithms": keyTypes,
"KexAlgorithms": kexAlgos,
"MACs": macAlgos,
"PubkeyAcceptedKeyTypes": keyTypes,
}

// validSshSingleVals are values that can accept a single string value from a static list.
validSshSingleVals = map[string][]string{
"AddKeysToAgent": {"yes", "ask", "confirm", "no"},
"AddressFamily": {"any", "inet", "inet6"},
"ControlMaster": {"ask", "yes", "no", "autoask", "auto"},
"FingerprintHash": {"sha256", "md5"},
}

// validSshMultiVals are values that can accept multiple values from a static list.
validSshMultiVals = map[string][]string{
"CASignatureAlgorithms": sigAlgos,
"Ciphers": ciphers,
"HostbasedKeyTypes": keyTypes,
"HostKeyAlgorithms": keyTypes,
"KbdInteractiveDevices": {"bsdauth", "pam"},
"KexAlgorithms": kexAlgos,
}
)

View File

@ -25,7 +25,7 @@ package config
*: These values are not in the upstream config but are allowed via the man page (sshd_config(5) and ssh_config(5)).
*/

// More or less a subset of SshdConf. These are valid keywords for Match blocks in sshd_config.
// SshdMatchRule is more or less a subset of SshdConf. These are valid keywords for Match blocks in sshd_config.
type SshdMatchRule struct {
AcceptEnv []string // *
AllowAgentForwarding sshBool // .
@ -49,7 +49,7 @@ type SshdMatchRule struct {
ForceCommand string // *
GatewayPorts string // .
GSSAPIAuthentication sshBool // .
HostbasedAcceptedKeyTypes []string // *
HostbasedAcceptedKeyTypes []string // *+
HostbasedAuthentication sshBool // .
HostbasedUsesNameFromPacketOnly sshBool // *
IgnoreRhosts string // .
@ -72,10 +72,10 @@ type SshdMatchRule struct {
PermitUserRC sshBool // *
PubkeyAcceptedKeyTypes []string // *
PubkeyAuthentication sshBool // .+
RekeyLimit string // .
RekeyLimit []string // .
RevokedKeys string // *
RDomain string // *
SetEnv map[string]string // *
SetEnv sshEnv // *
// max is 4095, it goes in the config as an octal.
StreamLocalBindMask uint16 // *
StreamLocalBindUnlink sshBool // *
@ -101,7 +101,7 @@ type SshdConf struct {
HostCertificate string // *
HostKeyAgent string // *
HostKeyAlgorithms []string // +*
HostKey []string // .
HostKey []string // .+
IgnoreUserKnownHosts sshBool // .
KerberosGetAFSToken sshBool // .
KerberosOrLocalPasswd sshBool // .
@ -114,7 +114,7 @@ type SshdConf struct {
MaxStartups string // .
PermitUserEnvironment sshBool // .
PidFile string // .
Port uint16 // .
Port []uint16 // .
PrintLastLog sshBool // .+
PrintMotd sshBool // .
Protocol int // +*
@ -131,20 +131,86 @@ type SshdConf struct {
XAuthLocation string // *
}

// SshConf represents an /etc/ssh/ssh_config (or ~/.ssh/config) file
type SshConf struct {
// These are in the default upstream sshd_config so we don't touch them. (Most, if not all, are commented out.)
// We just have them here to parse them.
Host map[string]string
}

// ListenAddr is a parsed ListenAddress directive.
type ListenAddr struct {
Addr string // hostname|address, hostname:port, IPv4_address:port, or [hostname|address]:port in conf string.
Port uint16
RDomain string
}

// MatchSshd is an sshd_config Match block.
type MatchSshd struct {
Criteria map[string]string
Rules []SshdMatchRule
}

// SshMatchRule is more or less a subset of SshConf. These are valid keywords for Match blocks in sshd_config.
type SshMatchRule struct {
}

// SshConf represents an /etc/ssh/ssh_config (or ~/.ssh/config) file
type SshConf struct {
AddKeysToAgent string // *
AddressFamily string // .
BatchMode sshBool // .
BindAddress string // *
BindInterface string // *
CanonicalDomains []string // *
CanonicalizeFallbackLocal sshBool // *
CanonicalizeHostname sshBool // *
CanonicalizeMaxDots uint8 // *
CanonicalizePermittedCNAMEs [][2]string // *
CASignatureAlgorithms []string // *
CertificateFile string // *
ChallengeResponseAuthentication sshBool // *
CheckHostIP sshBool // .+
Ciphers []string // .+
ClearAllForwardings sshBool // *
Compression sshBool // *
ConnectionAttempts uint16 // *
ConnectTimeout uint16 // .
ControlMaster string // *
ControlPath string // *
ControlPersist string // *
EnableSSHKeysign sshBool // *
EscapeChar string // .
ExitOnForwardFailure sshBool // *
FingerprintHash string // *
ForwardAgent string // .
ForwardXll sshBool // .
ForwardX11Timeout string // .
ForwardX11Trusted sshBool // *
GatewayPorts sshBool // *
GlobalKnownHostsFile []string // *
GSSAPIAuthentication sshBool // .
GSSAPIDelegateCredentials sshBool // .
HashKnownHosts sshBool // *+
Host []HostSsh // .
HostbasedAuthentication sshBool // .
HostbasedKeyTypes []string // *+
HostKeyAlgorithms []string // *+
HostKeyAlias string // *
Hostname string // *
IdentitiesOnly sshBool // *
IdentityAgent string // *
IdentityFile []string // .
IgnoreUnknown []string // *
Include string // *
IPQoS string // *
KbdInteractiveAuthentication sshBool // *
KbdInteractiveDevices []string // *
KexAlgorithms []string // *
LocalCommand string // *
LocalForward [2]string // *
}

// MatchSsh is an ssh_config Match block.
type MatchSsh struct {
Criteria map[string]string
Rules []SshMatchRule
}

type HostSsh struct {
Hostname string
Rules []SshMatchRule
}

View File

@ -1,5 +1,9 @@
package config

import (
`fmt`
)

type sshBool bool

func (b sshBool) Str() string {
@ -8,3 +12,13 @@ func (b sshBool) Str() string {
}
return "no"
}

type sshEnv map[string]string

func (e sshEnv) Str() string {
var s string
for k, v := range e {
s += fmt.Sprintf("%v=%v", k, v)
}
return s
}

View File

@ -1,34 +1,167 @@
package config

import (
"fmt"

"github.com/oleiade/reflections"
"github.com/pkg/errors"
`errors`
`fmt`
`reflect`
`strings`
)

var err error

func (c *SshdConf) Validate() (bool, error) {
for k, v := range validSshdSingleVals {
realV, err := reflections.GetField(c, k)
if err != nil {
return false, err
}
valid := false
for _, i := range v {
if i == realV {
valid = true
// TODO: Match block validation

// Have I mentioned how dumb golang is yet? The following methods share 90% of the same code,
// but ironically delegating that to a separate function is going to be even more of a PITA for now
// than just maintaining two separate methods.
// How dumb.

// Validate validates the actual struct of an SshdConf itself, ensuring that certain list fields contain
// valid item(s). It collects all found issues in allErr.
func (c *SshdConf) Validate() (validConf bool, allErr []error) {
// Set the default as true.
// We set it on false on first failed validation and keep going.
validConf = true
fields := reflect.TypeOf(c)
values := reflect.ValueOf(c)
for i := 0; i < fields.NumField(); i++ {
field := fields.Field(i)
value := values.Field(i)
var validField bool
n := field.Name
single, isSingle := validSshdSingleVals[n]
multi, isMulti := validSshdMultiVals[n]
if isSingle {
v := value.String()
for _, x := range single {
if v == x {
validField = true
break
}
}
if !valid {
if !validField {
validConf = false
e := fmt.Sprintf(
"field %v value %v is not allowed",
k, realV)
return false, errors.New(e)
"field %v: value %v is not allowed; must be ONE of: %v",
n, v, single,
)
allErr = append(allErr, errors.New(e))
}
} else if isMulti {
// These are all []strings if the field name is in validSshdMultiVals.
// But golang is still dumb and has no way of serializing directly, so here comes the hackery.
v, ok := value.Interface().([]string)
if !ok {
validConf = false
e := fmt.Sprintf(
"field %v: value %v was not able to be parsed as a []string",
n, v,
)
allErr = append(allErr, errors.New(e))
}
var invalidSlice []string
for _, vStr := range v {
validItem := false
for _, strip := range sshdStripPre {
if n == strip {
for _, c := range []string{"+", "-", "^"} {
vStr = strings.TrimPrefix(vStr, c)
}

return true, nil
}
}
for _, x := range multi {
if vStr == x {
validItem = true
}
}
if !validItem {
invalidSlice = append(invalidSlice, vStr)
}
}
for _, x := range invalidSlice {
e := fmt.Sprintf(
"field %v: value item %v is not valid; it must be one or more of %v",
n, x, multi,
)
allErr = append(allErr, errors.New(e))
}
}
}
return validConf, allErr
}

// Validate validates the actual struct of an SshConf itself, ensuring that certain list fields contain
// valid item(s). It collects all found issues in allErr.
func (c *SshConf) Validate() (validConf bool, allErr []error) {
// Set the default as true.
// We set it on false on first failed validation and keep going.
validConf = true
fields := reflect.TypeOf(c)
values := reflect.ValueOf(c)
for i := 0; i < fields.NumField(); i++ {
field := fields.Field(i)
value := values.Field(i)
var validField bool
n := field.Name
single, isSingle := validSshSingleVals[n]
multi, isMulti := validSshMultiVals[n]
if isSingle {
v := value.String()
for _, x := range single {
if v == x {
validField = true
break
}
}
if !validField {
validConf = false
e := fmt.Sprintf(
"field %v: value %v is not allowed; must be ONE of: %v",
n, v, single,
)
allErr = append(allErr, errors.New(e))
}
} else if isMulti {
// These are all []strings if the field name is in validSshMultiVals.
// But golang is still dumb and has no way of serializing directly, so here comes the hackery.
v, ok := value.Interface().([]string)
if !ok {
validConf = false
e := fmt.Sprintf(
"field %v: value %v was not able to be parsed as a []string",
n, v,
)
allErr = append(allErr, errors.New(e))
}
var invalidSlice []string
for _, vStr := range v {
validItem := false
for _, strip := range sshStripPre {
if n == strip {
for _, c := range []string{"+", "-", "^"} {
vStr = strings.TrimPrefix(vStr, c)
}

}
}
for _, x := range multi {
if vStr == x {
validItem = true
}
}
if !validItem {
invalidSlice = append(invalidSlice, vStr)
}
}
for _, x := range invalidSlice {
e := fmt.Sprintf(
"field %v: value item %v is not valid; it must be one or more of %v",
n, x, multi,
)
allErr = append(allErr, errors.New(e))
}
}
}
return validConf, allErr
}