all the directives are copied in with their types. working on validators now.
This commit is contained in:
268
config/const.go
Normal file
268
config/const.go
Normal 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
81
config/func.go
Normal 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
150
config/struct.go
Normal 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
10
config/type.go
Normal 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
34
config/validator.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user