checking in some config stuff
This commit is contained in:
parent
4b912a8dae
commit
d3a5f039c1
1
config/TODO
Normal file
1
config/TODO
Normal file
@ -0,0 +1 @@
|
||||
add default config as struct
|
@ -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,
|
||||
}
|
||||
)
|
||||
|
128
config/struct.go
128
config/struct.go
@ -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,33 +49,33 @@ type SshdMatchRule struct {
|
||||
ForceCommand string // *
|
||||
GatewayPorts string // .
|
||||
GSSAPIAuthentication sshBool // .
|
||||
HostbasedAcceptedKeyTypes []string // *
|
||||
HostbasedAcceptedKeyTypes []string // *+
|
||||
HostbasedAuthentication sshBool // .
|
||||
HostbasedUsesNameFromPacketOnly sshBool // *
|
||||
IgnoreRhosts string // .
|
||||
// Do we handle includes? Or just let sshd -T handle it?
|
||||
Include string // *
|
||||
// Accepts one or two. If two, first is interactive and second is non-interactive.
|
||||
IPQoS [2]string // *
|
||||
KbdInteractiveAuthentication sshBool // *
|
||||
KerberosAuthentication sshBool // .
|
||||
LogLevel string // .
|
||||
MaxAuthTries int // .
|
||||
MaxSessions int // .
|
||||
PasswordAuthentication sshBool // .+
|
||||
PermitEmptyPasswords sshBool // +
|
||||
PermitListen string // *
|
||||
PermitOpen string // *
|
||||
PermitRootLogin string // .+
|
||||
PermitTTY sshBool // .
|
||||
PermitTunnel string // .
|
||||
PermitUserRC sshBool // *
|
||||
PubkeyAcceptedKeyTypes []string // *
|
||||
PubkeyAuthentication sshBool // .+
|
||||
RekeyLimit string // .
|
||||
RevokedKeys string // *
|
||||
RDomain string // *
|
||||
SetEnv map[string]string // *
|
||||
IPQoS [2]string // *
|
||||
KbdInteractiveAuthentication sshBool // *
|
||||
KerberosAuthentication sshBool // .
|
||||
LogLevel string // .
|
||||
MaxAuthTries int // .
|
||||
MaxSessions int // .
|
||||
PasswordAuthentication sshBool // .+
|
||||
PermitEmptyPasswords sshBool // +
|
||||
PermitListen string // *
|
||||
PermitOpen string // *
|
||||
PermitRootLogin string // .+
|
||||
PermitTTY sshBool // .
|
||||
PermitTunnel string // .
|
||||
PermitUserRC sshBool // *
|
||||
PubkeyAcceptedKeyTypes []string // *
|
||||
PubkeyAuthentication sshBool // .+
|
||||
RekeyLimit []string // .
|
||||
RevokedKeys string // *
|
||||
RDomain 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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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
|
||||
break
|
||||
// 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 !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 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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
if !valid {
|
||||
e := fmt.Sprintf(
|
||||
"field %v value %v is not allowed",
|
||||
k, realV)
|
||||
return false, 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 true, nil
|
||||
return validConf, allErr
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user