all the directives are copied in with their types. working on validators now.

This commit is contained in:
2020-09-27 03:23:58 -04:00
parent c22786204a
commit 4b912a8dae
21 changed files with 859 additions and 131 deletions

268
config/const.go Normal file
View File

@@ -0,0 +1,268 @@
/*
SSHSecure - a program to harden OpenSSH from defaults
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package config
const (
// To check if this release of SSHSecure is up-to-date with the default values.
// upstreamSshdURL string = "https://anongit.mindrot.org/openssh.git/plain/sshd_config"
upstreamSshdURL string = "https://raw.githubusercontent.com/openssh/openssh-portable/master/sshd_config"
upstreamSshdCksum string = "952C844D7B36C54B03E2ADFB24860405" +
"1A702620A0ADC0738A8C30DC83D42A75" +
"27F5B3C184E779B1430168950F7695A1" +
"AA249F7CC719DEC1631ACFDDC2E8B653"
// upstreamSshURL string = "https://anongit.mindrot.org/openssh.git/plain/ssh_config"
upstreamSshURL string = "https://raw.githubusercontent.com/openssh/openssh-portable/master/ssh_config"
upstreamSshCksum string = "FF2D600465CC5D9CFBB57346491CCAF2" +
"C917E2F0C7B4D4EF6B851940948B55BD" +
"88205AC8153210ECA6C6BEA38E800F33" +
"7562C0190AE760220417A5DC2E00A5E1"
)
// These are items that this program modifies.
var (
// sshdModify are values we modify.
sshdModify = [...]string{
"HostKey",
"PermitRootLogin",
"StrictModes",
"PubkeyAuthentication",
"PasswordAuthentication",
"PermitEmptyPasswords",
"ChallengeResponseAuthentication",
"KexAlgorithms",
"Protocol",
"Ciphers",
"MACs",
}
// sshModify are values we modify.
sshModify = [...]string{""}
)
// These are collections of long lists of valid values.
var (
// sshdMulti are values that can be specified multiple times (multiple lines).
sshdMulti = [...]string{
"",
}
// authMethods are authentication methods that openssh supports.
authMethods = []string{
"any",
"keyboard-interactive",
"keyboard-interactive:bsdauth",
"keyboard-interactive:pam",
"gssapi-with-mic",
"hostbased",
"none",
"password",
"publickey",
}
// ciphers are cipher algorithms that openssh supports.
ciphers = []string{
"3des-cbc",
"aes128-cbc",
"aes192-cbc",
"aes256-cbc",
"aes128-ctr",
"aes192-ctr",
"aes256-ctr",
"aes128-gcm@openssh.com",
"aes256-gcm@openssh.com",
"chacha20-poly1305@openssh.com",
}
// forwardAllows are shared values used by forwarding access control.
forwardAllows = []string{"yes", "all", "no", "local", "remote"}
// hostkeyTypes are algorithms/types used for host keys.
// 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-ed25519",
"ssh-ed25519-cert-v01@openssh.com",
"sk-ssh-ed25519@openssh.com",
"sk-ssh-ed25519-cert-v01@openssh.com",
"ssh-rsa",
"rsa-sha2-256",
"rsa-sha2-512",
"ssh-dss",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"sk-ecdsa-sha2-nistp256@openssh.com",
"ssh-rsa-cert-v01@openssh.com",
"rsa-sha2-256-cert-v01@openssh.com",
"rsa-sha2-512-cert-v01@openssh.com",
"ssh-dss-cert-v01@openssh.com",
"ecdsa-sha2-nistp256-cert-v01@openssh.com",
"ecdsa-sha2-nistp384-cert-v01@openssh.com",
"ecdsa-sha2-nistp521-cert-v01@openssh.com",
"sk-ecdsa-sha2-nistp256-cert-v01@openssh.com",
}
// ipQoS is a list of valid QoS profiles.
ipQoS = []string{
// This also supports a "numeric value" per sshd_config(5),
// but I have no idea what those values are, their range, etc.
// So strings only. Makes for more readable configs anyways.
// TODO: is this specified in the source anywhere?
"af11",
"af12",
"af13",
"af21",
"af22",
"af23",
"af31",
"af32",
"af33",
"af41",
"af42",
"af43",
"cs0",
"cs1",
"cs2",
"cs3",
"cs4",
"cs5",
"cs6",
"cs7",
"ef",
"le",
"lowdelay",
"throughput",
"reliability",
"none",
}
// kexAlgos is a lost of valid kex ("KEy eXchange") algorithms.
// ssh -Q kex | sed -re 's/^/"/g' -e 's/$/",/g'
kexAlgos = []string{
"diffie-hellman-group1-sha1",
"diffie-hellman-group14-sha1",
"diffie-hellman-group14-sha256",
"diffie-hellman-group16-sha512",
"diffie-hellman-group18-sha512",
"diffie-hellman-group-exchange-sha1",
"diffie-hellman-group-exchange-sha256",
"ecdh-sha2-nistp256",
"ecdh-sha2-nistp384",
"ecdh-sha2-nistp521",
"curve25519-sha256",
"curve25519-sha256@libssh.org",
"sntrup4591761x25519-sha512@tinyssh.org",
}
// sigAlgos is a list of valid algorithms for CA signatures.
sigAlgos = []string{
// These are the defaults. TODO: are any others valid?
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
"ecdsa-sha2-nistp521",
"ssh-ed25519",
"rsa-sha2-512",
"rsa-sha2-256",
"ssh-rsa",
}
// logLevels is a list of valid LogLevel levels.
logLevels = []string{
"QUIET",
"FATAL",
"ERROR",
"INFO", // default
"VERBOSE",
"DEBUG", // same as DEBUG1
"DEBUG1", // same as DEBUG
"DEBUG2",
"DEBUG3",
}
// 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{
"hmac-sha1",
"hmac-sha1-96",
"hmac-sha2-256",
"hmac-sha2-512",
"hmac-md5",
"hmac-md5-96",
"umac-64@openssh.com",
"umac-128@openssh.com",
"hmac-sha1-etm@openssh.com",
"hmac-sha1-96-etm@openssh.com",
"hmac-sha2-256-etm@openssh.com",
"hmac-sha2-512-etm@openssh.com",
"hmac-md5-etm@openssh.com",
"hmac-md5-96-etm@openssh.com",
"umac-64-etm@openssh.com",
"umac-128-etm@openssh.com",
}
)
// This is a collection related to Match blocks.
var (
// sshdMatchCriteria is a list of valid criteria that can be used in a Match block.
// 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{
"All",
"User",
"Group",
"Host",
"LocalAddress",
"LocalPort",
"RDomain",
"Address",
}
)
// 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?
sshdStripPre = []string{
"HostbasedAcceptedKeyTypes",
"KexAlgorithms",
}
// validSshdSingleVals are values that can accept a single string value from a static list.
validSshdSingleVals = map[string][]string{
"AddressFamily": {"any", "inet", "inet6"},
"AllowStreamLocalForwarding": forwardAllows,
"AllowTcpForwarding": forwardAllows,
"Compression": {"yes", "delayed", "no"}, // "delayed" is legacy, same as "yes".
"FingerprintHash": {"sha256", "md5"},
"GatewayPorts": {"yes", "no", "clientspecified"},
"IgnoreRHosts": {"yes", "shosts-only", "no"},
"LogLevel": logLevels,
}
// validSshdMultiVals are values that can accept multiple values from a static list.
validSshdMultiVals = map[string][]string{
"AuthenticationMethods": authMethods,
"CASignatureAlgorithms": sigAlgos,
"Ciphers": ciphers,
"HostbasedAcceptedKeyTypes": hostkeyTypes, // NOTE: Can also begin with "+", "-", or "^"
"HostKeyAlgorithms": hostkeyTypes,
"KexAlgorithms": kexAlgos, // NOTE: Can also begin with "+", "-", or "^"
"MACs": macAlgos,
}
)

81
config/func.go Normal file
View File

@@ -0,0 +1,81 @@
/*
SSHSecure - a program to harden OpenSSH from defaults
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package config
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"github.com/pkg/errors"
"r00t2.io/sysutils/exec_extra"
"r00t2.io/sysutils/paths"
)
func TestConfig(config *[]byte) (bool, error) {
var sysPaths []string
var binPath string
var tmpConf *os.File
var err error
var stdout, stderr bytes.Buffer
// sshd *requires* to be invoked with an absolute path.
sysPaths, err = paths.GetPathEnv()
if err != nil {
return false, err
}
for _, p := range sysPaths {
fpath := path.Join(p, "sshd")
if exists, err := paths.RealPathExists(&fpath); err != nil {
return false, err
} else if !exists {
continue
}
binPath = fpath
break
}
tmpConf, err = ioutil.TempFile("/tmp", ".test.sshconf.")
if err != nil {
return false, err
}
defer os.Remove(tmpConf.Name())
if err = tmpConf.Chmod(0600); err != nil {
return false, err
}
if _, err = tmpConf.Write(*config); err != nil {
return false, err
}
cmd := exec.Command(binPath,
"-T", fmt.Sprintf("-f %v",
tmpConf.Name()))
cmd.Stdout = &stdout
cmd.Stderr = &stderr
exitStatus, err := exec_extra.ExecCmdReturn(cmd)
if err != nil {
return false, err
}
if exitStatus != 0 {
// TODO: also handle non-empty stderr?
e := fmt.Sprintf("returned status/exit code %d", exitStatus)
return false, errors.New(e)
}
return true, nil
}

150
config/struct.go Normal file
View File

@@ -0,0 +1,150 @@
/*
SSHSecure - a program to harden OpenSSH from defaults
Copyright (C) 2020 Brent Saner
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package config
/*
NOTATION KEY:
.: Exists in default upstream config (but usually they're commented out)
+: These values are/may be modified by this program.
*: 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.
type SshdMatchRule struct {
AcceptEnv []string // *
AllowAgentForwarding sshBool // .
AllowGroups []string // *
AllowStreamLocalForwarding string // *
AllowTcpForwarding string // .
AllowUsers []string // *
AuthenticationMethods []string // +*
AuthorizedKeysCommand string // .
AuthorizedKeysCommandUser string // .
AuthorizedKeysFile string // .
AuthorizedPrincipalsCommand string // *
AuthorizedPrincipalsCommandUser string // *
AuthorizedPrincipalsFile string // .
Banner string // .
ChrootDirectory string // .
ClientAliveCountMax int // .
ClientAliveInterval int // .
DenyGroups []string // *
DenyUsers []string // *
ForceCommand string // *
GatewayPorts string // .
GSSAPIAuthentication sshBool // .
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 // *
// max is 4095, it goes in the config as an octal.
StreamLocalBindMask uint16 // *
StreamLocalBindUnlink sshBool // *
TrustedUserCAKeys string // *
X11DisplayOffset int // .
X11Forwarding sshBool // .
}
// SshdConf represents an /etc/ssh/sshd_config file's directives/values.
// Values in SshdMatchRule are not reproduced here.
type SshdConf struct {
SshdMatchRule
AddressFamily string // .
CASignatureAlgorithms []string // *
ChallengeResponseAuthentication sshBool // .+
Ciphers []string // +*
Compression string // .
DisableForwarding sshBool // *
ExposeAuthInfo sshBool // *
FingerprintHash string // *
GSSAPICleanupCredentials sshBool // .
GSSAPIStrictAcceptorCheck sshBool // *
HostCertificate string // *
HostKeyAgent string // *
HostKeyAlgorithms []string // +*
HostKey []string // .
IgnoreUserKnownHosts sshBool // .
KerberosGetAFSToken sshBool // .
KerberosOrLocalPasswd sshBool // .
KerberosTicketCleanup sshBool // .
KexAlgorithms string // +*
ListenAddress ListenAddr // .
LoginGraceTime string // .
MACs []string // +*
Match map[string]string // .
MaxStartups string // .
PermitUserEnvironment sshBool // .
PidFile string // .
Port uint16 // .
PrintLastLog sshBool // .+
PrintMotd sshBool // .
Protocol int // +*
PubkeyAuthOptions string // *
SecurityKeyProvider string // *
StrictModes sshBool // .+
Subsystem string // .
SyslogFacility string // .
TCPKeepAlive sshBool // .
UseDNS sshBool // .
UsePAM sshBool // .
VersionAddendum string // .
X11UseLocalhost sshBool // .
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
}
type ListenAddr struct {
Addr string // hostname|address, hostname:port, IPv4_address:port, or [hostname|address]:port in conf string.
Port uint16
RDomain string
}
type MatchSshd struct {
Criteria map[string]string
Rules []SshdMatchRule
}

10
config/type.go Normal file
View File

@@ -0,0 +1,10 @@
package config
type sshBool bool
func (b sshBool) Str() string {
if b {
return "yes"
}
return "no"
}

34
config/validator.go Normal file
View File

@@ -0,0 +1,34 @@
package config
import (
"fmt"
"github.com/oleiade/reflections"
"github.com/pkg/errors"
)
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
}
}
if !valid {
e := fmt.Sprintf(
"field %v value %v is not allowed",
k, realV)
return false, errors.New(e)
}
}
return true, nil
}