all the directives are copied in with their types. working on validators now.
This commit is contained in:
parent
c22786204a
commit
4b912a8dae
@ -54,7 +54,8 @@ no identifying information other than the fact that you are using SSHSecure.
|
||||
It will create backups of any file(s) it replaces and automatically rolls back `sshd`
|
||||
configuration changes if it does not pass the syntax check (`sshd -t`) to avoid
|
||||
accidentally locking you out.
|
||||
|
||||
|
||||
<!--
|
||||
The first time you run it, it will quite possibly take a **very** long time. This is
|
||||
because it's generating fresh DH parameters, which is a very time-consuming process.
|
||||
Subsequent runs will not take as long, however, as checks are put in place to determine
|
||||
@ -62,6 +63,7 @@ if custom DH parameters have been generated or not yet. If it's running on a GNU
|
||||
system and you have [`haveged`](http://www.issihosts.com/haveged/) installed, that will
|
||||
significantly speed up the process (SSHSecure will start it automatically if it isn't
|
||||
running already).
|
||||
-->
|
||||
|
||||
## FAQ
|
||||
|
||||
|
29
TODO
29
TODO
@ -1,27 +1,24 @@
|
||||
-sshkeys (see ref/<type>/parse_poc_<keytype>.go for POC)
|
||||
--hostkeys (https://security.stackexchange.com/questions/211106/what-is-the-difference-between-host-and-client-ssh-key-generation)?
|
||||
-moduli dhparams generation (dh.c? moduli.c?)
|
||||
--ssh-keygen.c, ~L3565
|
||||
|
||||
- General/common
|
||||
-- Locking?
|
||||
-- Constants for common file dests
|
||||
-- Func to write to dest, backing up dest if exists first
|
||||
-- Test ssh config (sshd -t) and rollback if fail
|
||||
-- HALF-DONE: Locking?
|
||||
-- DONE: Constants for common file dests
|
||||
-- DONE: Func to write to dest, backing up dest if exists first
|
||||
-- DONE: Test ssh config (sshd -t) and rollback if fail
|
||||
-- When completely done, go.mod
|
||||
|
||||
- Key generation
|
||||
-- DONE: Generate priv/pubkeys
|
||||
-- Build key structure
|
||||
-- DONE: Build key structure
|
||||
-- write out base64 with headers to files
|
||||
|
||||
- SSH Moduli
|
||||
-- Do DH param gen in goroutine so we can do other things while it spawns and runs
|
||||
-- Check if haveged is running. If not and installed, start it.
|
||||
-- Generate moduli
|
||||
-- Render to /etc/ssh/moduli format
|
||||
--- custom moduli marshaler/unmarshaler? (e.g. https://stackoverflow.com/a/50211222)
|
||||
-- NOT NEEDED?: Do DH param gen in goroutine so we can do other things while it spawns and runs
|
||||
--- Check if haveged is running. If not and installed, start it.
|
||||
--- Generate moduli
|
||||
-- DONE: Render to /etc/ssh/moduli format
|
||||
--- DONE: custom moduli marshaler/unmarshaler? (e.g. https://stackoverflow.com/a/50211222)
|
||||
-- Write to dest
|
||||
|
||||
- Config
|
||||
-- Need to merge in changes
|
||||
-- Track options in struct?
|
||||
-- Track options in struct?
|
||||
--- add subtype structs
|
||||
|
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
|
||||
}
|
9
go.mod
9
go.mod
@ -1,9 +0,0 @@
|
||||
module r00t2.io/sshsecure
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a
|
||||
github.com/go-restruct/restruct v1.2.0-alpha
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
)
|
24
go.sum
24
go.sum
@ -1,24 +0,0 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a h1:saTgr5tMLFnmy/yg3qDTft4rE5DY2uJ/cCxCe3q0XTU=
|
||||
github.com/dchest/bcrypt_pbkdf v0.0.0-20150205184540-83f37f9c154a/go.mod h1:Bw9BbhOJVNR+t0jCqx2GC6zv0TGBsShs56Y3gfSCvl0=
|
||||
github.com/go-restruct/restruct v1.2.0-alpha h1:2Lp474S/9660+SJjpVxoKuWX09JsXHSrdV7Nv3/gkvc=
|
||||
github.com/go-restruct/restruct v1.2.0-alpha/go.mod h1:KqrpKpn4M8OLznErihXTGLlsXFGeLxHUrLRRI/1YjGk=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -27,7 +27,8 @@ import (
|
||||
// Misc
|
||||
const (
|
||||
// Where to find an up-to-date copy of the upstream moduli and its SHA3-512 (NIST) checksum.
|
||||
pregenURL string = "https://anongit.mindrot.org/openssh.git/tree/moduli"
|
||||
// pregenURL string = "https://anongit.mindrot.org/openssh.git/plain/moduli"
|
||||
pregenURL string = "https://raw.githubusercontent.com/openssh/openssh-portable/master/moduli"
|
||||
// This is the best way I could think of to verify integrity, since the file itself doesn't have a signature or anything like that.
|
||||
pregenCksum string = "106EDB19A936608D065D2E8E81F7BDE7" +
|
||||
"434AF80EF81102E9440B99ACB98FBEF8" +
|
||||
@ -37,7 +38,8 @@ const (
|
||||
parseTag string = "sshmoduli"
|
||||
// The recommended minimum moduli to have available.
|
||||
recMinMod int = 400
|
||||
// The
|
||||
// The minimum bits for filtering. It's generally bits - 1
|
||||
minBits uint8 = 4095
|
||||
)
|
||||
|
||||
// The header line on the /etc/ssh/moduli file.
|
||||
@ -55,7 +57,8 @@ const (
|
||||
timeFormat string = "20060102150405" // %Y%m%d%H%M%S
|
||||
)
|
||||
|
||||
// For validation
|
||||
// For validation. Currently unused.
|
||||
/*
|
||||
var (
|
||||
validTypes = []uint8{
|
||||
0, // Unknown, not tested
|
||||
@ -69,3 +72,4 @@ var (
|
||||
0x04, // Probabilistic Miller-Rabin primality tests.
|
||||
}
|
||||
)
|
||||
*/
|
||||
|
@ -28,33 +28,38 @@ import (
|
||||
"golang.org/x/crypto/sha3"
|
||||
)
|
||||
|
||||
func getPregen() ([]byte, error) {
|
||||
// getPregen gets the pregenerated moduli from upstream mirror.
|
||||
func getPregen() (Moduli, error) {
|
||||
m := Moduli{}
|
||||
// get the pregenerated moduli
|
||||
resp, err := http.Get(pregenURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return m, err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New(fmt.Sprintf("returned status code %v: %v", resp.StatusCode, resp.Status))
|
||||
return m, errors.New(fmt.Sprintf("returned status code %v: %v", resp.StatusCode, resp.Status))
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
b := make([]byte, resp.ContentLength)
|
||||
if _, err = resp.Body.Read(b); err != nil {
|
||||
return nil, err
|
||||
return m, err
|
||||
}
|
||||
// and compare the SHA3-512 (NIST) checksum.
|
||||
s := sha3.New512()
|
||||
if _, err = s.Write(b); err != nil {
|
||||
// TODO: return nil instead of b?
|
||||
return b, err
|
||||
return m, err
|
||||
}
|
||||
goodCksum, err := hex.DecodeString(pregenCksum)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return m, err
|
||||
}
|
||||
// We just compare the bytestrings.
|
||||
if bytes.Compare(s.Sum(nil), goodCksum) != 0 {
|
||||
return nil, errors.New("checksums do not match")
|
||||
return m, errors.New("checksums do not match")
|
||||
}
|
||||
return b, nil
|
||||
if err := Unmarshal(b, m); err != nil {
|
||||
return m, err
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
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 moduli
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
@ -50,7 +50,7 @@ func (m *Moduli) Marshal() ([]byte, error) {
|
||||
}
|
||||
|
||||
// marshalEntry is used to parse a specific DH entry into the moduli.
|
||||
func (m *ModuliEntry) marshalEntry() ([]byte, error) {
|
||||
func (m *Entry) marshalEntry() ([]byte, error) {
|
||||
mod := hex.EncodeToString(m.Modulus.Bytes())
|
||||
s := fmt.Sprintf(
|
||||
"%v %v %v %v %v %v %v\n",
|
||||
@ -68,10 +68,10 @@ func (m *ModuliEntry) marshalEntry() ([]byte, error) {
|
||||
// Unmarshal writes the Moduli format into m from the /etc/ssh/moduli format in data.
|
||||
func Unmarshal(data []byte, m Moduli) error {
|
||||
var lines []string
|
||||
var entries []ModuliEntry
|
||||
var entries []Entry
|
||||
lines = strings.Split(string(data), "\n")
|
||||
for _, line := range lines {
|
||||
e := ModuliEntry{}
|
||||
e := Entry{}
|
||||
if reSkipLine.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
@ -85,7 +85,8 @@ func Unmarshal(data []byte, m Moduli) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func unmarshalEntry(line []string, m ModuliEntry) error {
|
||||
func unmarshalEntry(line []string, m Entry) error {
|
||||
var err error
|
||||
if len(line) != 7 {
|
||||
return errors.New("field count mismatch")
|
||||
}
|
||||
@ -117,3 +118,19 @@ func unmarshalEntry(line []string, m ModuliEntry) error {
|
||||
m.Modulus.SetBytes(modb)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Moduli) Harden() error {
|
||||
var entries []Entry
|
||||
for _, e := range m.Params {
|
||||
if e.Size >= minBits {
|
||||
entries = append(entries, e)
|
||||
}
|
||||
}
|
||||
m.Params = entries
|
||||
if len(m.Params) < recMinMod {
|
||||
return errors.New("does not meet recommended minimum moduli")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: find way of testing/sieving primes
|
||||
|
@ -26,11 +26,11 @@ import (
|
||||
// Moduli contains all data needed for generated /etc/ssh/moduli. of ModuliEntry entries.
|
||||
type Moduli struct {
|
||||
Header string
|
||||
Params []ModuliEntry
|
||||
Params []Entry
|
||||
}
|
||||
|
||||
// Moduli is a struct reflecting the format of a single /etc/ssh/moduli entry. See moduli(5) for details.
|
||||
type ModuliEntry struct {
|
||||
type Entry struct {
|
||||
Time time.Time // YYYYMMDDHHSS
|
||||
/*
|
||||
// man 5 moduli:
|
||||
|
@ -1,3 +1,21 @@
|
||||
/*
|
||||
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 sharedconsts
|
||||
|
||||
import (
|
||||
@ -11,3 +29,22 @@ const (
|
||||
)
|
||||
|
||||
var IDCmnt = string(fmt.Sprintf("Autogenerated by %v (%v)", projName, projUrl))
|
||||
|
||||
// Common file/directory paths.
|
||||
const (
|
||||
// System-wide files.
|
||||
LockFile string = "/tmp/.sshsecure.lck"
|
||||
SysSshConfDir string = "/etc/ssh"
|
||||
// The following are joined with SysSshConfDir.
|
||||
SshdConfFile string = "sshd_config"
|
||||
SshConfFile string = "ssh_config"
|
||||
HostEd25519File string = "ssh_host_ed25519_key"
|
||||
HostRsaFile string = "ssh_host_rsa_key"
|
||||
ModuliFile string = "moduli"
|
||||
// Invoking user's files.
|
||||
UserSshConfDir string = "~/.ssh"
|
||||
// The following are joined with userSshConfDir.
|
||||
UserSshConfFile string = "config"
|
||||
UserEd25519File string = "id_ed25519"
|
||||
UserRsaFile string = "id_rsa"
|
||||
)
|
||||
|
@ -43,7 +43,7 @@ const (
|
||||
CipherAes256Ctr string = "aes256-ctr"
|
||||
)
|
||||
|
||||
var allowed_ciphers = [...]string{CipherNull, CipherAes256Ctr}
|
||||
var allowedCiphers = [...]string{CipherNull, CipherAes256Ctr}
|
||||
|
||||
// Key types.
|
||||
const (
|
||||
@ -51,7 +51,7 @@ const (
|
||||
KeyRsa string = "ssh-rsa"
|
||||
)
|
||||
|
||||
var allowed_keytypes = [...]string{KeyEd25519, KeyRsa}
|
||||
var allowedKeytypes = [...]string{KeyEd25519, KeyRsa}
|
||||
|
||||
// KDF names. I believe only bcrypt is supported upstream currently.
|
||||
const (
|
||||
@ -59,7 +59,7 @@ const (
|
||||
KdfBcrypt string = "bcrypt"
|
||||
)
|
||||
|
||||
var allowed_kdfnames = [...]string{KdfNull, KdfBcrypt}
|
||||
var allowedKdfnames = [...]string{KdfNull, KdfBcrypt}
|
||||
|
||||
// Key lengths.
|
||||
const (
|
||||
|
@ -1,12 +1,30 @@
|
||||
/*
|
||||
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 sshkeys
|
||||
|
||||
import (
|
||||
`crypto/aes`
|
||||
`crypto/cipher`
|
||||
`errors`
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"errors"
|
||||
|
||||
// `golang.org/x/crypto/ssh/internal/bcrypt_pbkdf` Golang doesn't let you import "internal" libs. Fine. Lame language.
|
||||
`github.com/dchest/bcrypt_pbkdf`
|
||||
"github.com/dchest/bcrypt_pbkdf"
|
||||
)
|
||||
|
||||
func (k *EncryptedSSHKeyV1) setCrypt() error {
|
||||
@ -22,6 +40,7 @@ func (k *EncryptedSSHKeyV1) setCrypt() error {
|
||||
}
|
||||
|
||||
func (k *EncryptedSSHKeyV1) setCipher() error {
|
||||
var err error
|
||||
switch k.CipherName {
|
||||
case CipherAes256Ctr:
|
||||
if k.Crypt.Cipher, err = aes.NewCipher(k.Crypt.PrivateKey); err != nil {
|
||||
@ -37,14 +56,15 @@ func (k *EncryptedSSHKeyV1) setCipher() error {
|
||||
}
|
||||
|
||||
func (k *EncryptedSSHKeyV1) setKDF() error {
|
||||
var err error
|
||||
// Upstream only currently supports bcrypt_pbkdf ("bcrypt").
|
||||
// This should always eval to true, but is here for future planning in case other KDF are implemented.
|
||||
switch k.KDFName {
|
||||
case KdfBcrypt:
|
||||
if k.Crypt.CryptKey, err = bcrypt_pbkdf.Key(k.Passphrase,
|
||||
k.KDFOpts.Salt,
|
||||
int(k.KDFOpts.Rounds),
|
||||
kdfKeyLen + len(k.KDFOpts.Salt),
|
||||
k.KDFOpts.Salt,
|
||||
int(k.KDFOpts.Rounds),
|
||||
kdfKeyLen+len(k.KDFOpts.Salt),
|
||||
); err != nil {
|
||||
return err
|
||||
} else {
|
||||
@ -54,4 +74,5 @@ func (k *EncryptedSSHKeyV1) setKDF() error {
|
||||
default:
|
||||
return errors.New("could not find KDF")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"r00t2.io/sshsecure/sharedconsts"
|
||||
)
|
||||
@ -34,19 +33,19 @@ func (k *EncryptedSSHKeyV1) validate() error {
|
||||
var validCipher bool
|
||||
var validKDF bool
|
||||
var validKT bool
|
||||
for _, v := range allowed_ciphers {
|
||||
for _, v := range allowedCiphers {
|
||||
if v == k.CipherName {
|
||||
validCipher = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, v := range allowed_kdfnames {
|
||||
for _, v := range allowedKdfnames {
|
||||
if v == k.KDFName {
|
||||
validKDF = true
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, v := range allowed_keytypes {
|
||||
for _, v := range allowedKeytypes {
|
||||
if v == k.DefKeyType {
|
||||
validKT = true
|
||||
}
|
||||
@ -107,7 +106,7 @@ func (k *EncryptedSSHKeyV1) Generate(force bool) error {
|
||||
return errors.New("unknown key type; could not generate private/public keypair")
|
||||
}
|
||||
k.Keys = append(k.Keys, pk)
|
||||
// We also need an encrypter/decrypter since this is an encrypted key.
|
||||
// We also need an encryptor/decryptor since this is an encrypted key.
|
||||
if err := k.setCrypt(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -120,7 +119,7 @@ func (k *EncryptedSSHKeyV1) Generate(force bool) error {
|
||||
|
||||
func (k *SSHKeyV1) validate() error {
|
||||
var validKT bool
|
||||
for _, v := range allowed_keytypes {
|
||||
for _, v := range allowedKeytypes {
|
||||
if v == k.DefKeyType {
|
||||
validKT = true
|
||||
}
|
||||
@ -143,7 +142,7 @@ func (k *SSHKeyV1) Generate(force bool) error {
|
||||
// Currently, OpenSSH has an option for multiple private keys. However, it is hardcoded to 1.
|
||||
// If multiple key support is added in the future, will need to re-tool how I do this, perhaps, in the future. TODO.
|
||||
pk := SSHPrivKey{
|
||||
Comment: fmt.Sprintf("Autogenerated via SSHSecure (%v)", projUrl),
|
||||
Comment: sharedconsts.IDCmnt,
|
||||
}
|
||||
pk.Checksum = make([]byte, 4)
|
||||
if _, err := rand.Read(pk.Checksum); err != nil {
|
||||
|
@ -1,23 +0,0 @@
|
||||
/*
|
||||
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 sshkeys
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
172
utils/files.go
Normal file
172
utils/files.go
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 utils
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
gopath "path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"r00t2.io/sysutils/paths"
|
||||
|
||||
"r00t2.io/sshsecure/sharedconsts"
|
||||
)
|
||||
|
||||
func backupWrite(path string, data []byte, system bool, force bool) error {
|
||||
var err error
|
||||
var uid int
|
||||
var gid int
|
||||
var running bool
|
||||
var runningInfo runInfo
|
||||
var curInfo runInfo
|
||||
uid, gid, err = getPerms(system)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = paths.RealPath(&path); err != nil {
|
||||
return err
|
||||
}
|
||||
curInfo = getInfo()
|
||||
parent := filepath.Dir(path)
|
||||
parentExists, err := paths.RealPathExists(&parent)
|
||||
infoFile := gopath.Join(
|
||||
parent,
|
||||
fmt.Sprintf(".%v.gob",
|
||||
filepath.Base(path),
|
||||
))
|
||||
if err = paths.MakeDirIfNotExist(&parent); err != nil {
|
||||
return err
|
||||
}
|
||||
exists, err := paths.RealPathExists(&path)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !parentExists {
|
||||
// It exists *now*, but it's a new dir. We set perms thusly.
|
||||
os.Chmod(parent, 0700)
|
||||
}
|
||||
if gobExists, err := paths.RealPathExists(&infoFile); err != nil {
|
||||
return err
|
||||
} else if gobExists && !force {
|
||||
return nil // Don't go any further; the file was already generated.
|
||||
}
|
||||
running, runningInfo, err = isRunning()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists && running {
|
||||
e := fmt.Sprintf(
|
||||
"refusing to write to %v; another instance of program is currently running with PID %v started at %v",
|
||||
path, runningInfo.Pid, runningInfo.TimeStarted)
|
||||
return errors.New(e)
|
||||
} else if exists && !running {
|
||||
newName := fmt.Sprintf("%v.bak.%d", path, curInfo.TimeStarted.Unix())
|
||||
if err = os.Rename(path, newName); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// FINALLY. The business end.
|
||||
w, err := os.Create(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gobFile, err := os.Create(infoFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
g := gob.NewEncoder(gobFile)
|
||||
defer w.Close()
|
||||
defer gobFile.Close()
|
||||
if err = w.Chmod(0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = w.Chown(uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = w.Write(data); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = gobFile.Chmod(0600); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = gobFile.Chown(uid, gid); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = g.Encode(curInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getInfo() runInfo {
|
||||
curInfo := runInfo{
|
||||
TimeStarted: getTime(),
|
||||
Pid: getPid(),
|
||||
}
|
||||
return curInfo
|
||||
}
|
||||
|
||||
func getPid() int {
|
||||
return os.Getpid()
|
||||
}
|
||||
|
||||
func getPerms(system bool) (int, int, error) {
|
||||
var uid int
|
||||
var gid int
|
||||
if system {
|
||||
if os.Geteuid() != 0 {
|
||||
return 0, 0, errors.New("cannot create system file without root permissions")
|
||||
}
|
||||
uid = 0
|
||||
gid = 0
|
||||
} else {
|
||||
uid = os.Geteuid()
|
||||
gid = os.Getegid()
|
||||
}
|
||||
return uid, gid, nil
|
||||
}
|
||||
|
||||
func getTime() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
func isRunning() (bool, runInfo, error) {
|
||||
curInfo := getInfo()
|
||||
var fileInfo runInfo
|
||||
lckFile := sharedconsts.LockFile
|
||||
exists, _ := paths.RealPathExists(&lckFile)
|
||||
if !exists {
|
||||
// This should never happen. If it does, I goofed the locking (or it's not invoked from a shell).
|
||||
return false, fileInfo, errors.New("*grabs shotgun* computer's haunted")
|
||||
}
|
||||
reader, err := os.Open(lckFile)
|
||||
if err != nil {
|
||||
return false, fileInfo, err
|
||||
}
|
||||
defer reader.Close()
|
||||
gobDecoder := gob.NewDecoder(reader)
|
||||
gobDecoder.Decode(&fileInfo)
|
||||
if fileInfo != curInfo {
|
||||
return true, fileInfo, nil
|
||||
}
|
||||
return false, fileInfo, nil
|
||||
}
|
10
utils/struct.go
Normal file
10
utils/struct.go
Normal file
@ -0,0 +1,10 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type runInfo struct {
|
||||
TimeStarted time.Time
|
||||
Pid int
|
||||
}
|
Loading…
Reference in New Issue
Block a user