add support for ssh ca auth (#467)
This commit is contained in:
parent
a3f69952c0
commit
ed19d555b5
3 changed files with 174 additions and 18 deletions
|
|
@ -58,6 +58,14 @@ pipes:
|
||||||
username: "user"
|
username: "user"
|
||||||
ignore_hostkey: true
|
ignore_hostkey: true
|
||||||
private_key: {{ .PrivateKey }}
|
private_key: {{ .PrivateKey }}
|
||||||
|
- from:
|
||||||
|
- username: "cert"
|
||||||
|
trusted_user_ca_keys: {{ .TrustedUserCAKeys }}
|
||||||
|
to:
|
||||||
|
host: host-publickey:2222
|
||||||
|
username: "user"
|
||||||
|
ignore_hostkey: true
|
||||||
|
private_key: {{ .PrivateKey }}
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestYaml(t *testing.T) {
|
func TestYaml(t *testing.T) {
|
||||||
|
|
@ -125,6 +133,43 @@ func TestYaml(t *testing.T) {
|
||||||
); err != nil {
|
); err != nil {
|
||||||
t.Errorf("failed to copy public key: %v", err)
|
t.Errorf("failed to copy public key: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ssh ca
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-f",
|
||||||
|
path.Join(yamldir, "ca_key"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to generate ca key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-N",
|
||||||
|
"",
|
||||||
|
"-f",
|
||||||
|
path.Join(yamldir, "user_ca_key"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to generate user ca key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := runCmdAndWait(
|
||||||
|
"ssh-keygen",
|
||||||
|
"-s",
|
||||||
|
path.Join(yamldir, "ca_key"),
|
||||||
|
"-I",
|
||||||
|
"cert",
|
||||||
|
"-n",
|
||||||
|
"cert",
|
||||||
|
"-V",
|
||||||
|
"+1w",
|
||||||
|
path.Join(yamldir, "user_ca_key.pub"),
|
||||||
|
); err != nil {
|
||||||
|
t.Errorf("failed to sign user ca key: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
knownHostsKeyData, err := runAndGetStdout(
|
knownHostsKeyData, err := runAndGetStdout(
|
||||||
|
|
@ -155,6 +200,8 @@ func TestYaml(t *testing.T) {
|
||||||
|
|
||||||
AuthorizedKeys_Simple string
|
AuthorizedKeys_Simple string
|
||||||
AuthorizedKeys_Catchall string
|
AuthorizedKeys_Catchall string
|
||||||
|
|
||||||
|
TrustedUserCAKeys string
|
||||||
}{
|
}{
|
||||||
KnownHostsKey: base64.StdEncoding.EncodeToString(knownHostsKeyData),
|
KnownHostsKey: base64.StdEncoding.EncodeToString(knownHostsKeyData),
|
||||||
KnownHostsPass: base64.StdEncoding.EncodeToString(knownHostsPassData),
|
KnownHostsPass: base64.StdEncoding.EncodeToString(knownHostsPassData),
|
||||||
|
|
@ -162,6 +209,8 @@ func TestYaml(t *testing.T) {
|
||||||
|
|
||||||
AuthorizedKeys_Simple: path.Join(yamldir, "id_rsa_simple.pub"),
|
AuthorizedKeys_Simple: path.Join(yamldir, "id_rsa_simple.pub"),
|
||||||
AuthorizedKeys_Catchall: path.Join(yamldir, "id_rsa_catchall.pub"),
|
AuthorizedKeys_Catchall: path.Join(yamldir, "id_rsa_catchall.pub"),
|
||||||
|
|
||||||
|
TrustedUserCAKeys: path.Join(yamldir, "ca_key.pub"),
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
t.Fatalf("Failed to write yaml file %v", err)
|
t.Fatalf("Failed to write yaml file %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -397,4 +446,37 @@ func TestYaml(t *testing.T) {
|
||||||
checkSharedFileContent(t, targetfie, randtext)
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("ssh_cert", func(t *testing.T) {
|
||||||
|
randtext := uuid.New().String()
|
||||||
|
targetfie := uuid.New().String()
|
||||||
|
|
||||||
|
c, _, _, err := runCmd(
|
||||||
|
"ssh",
|
||||||
|
"-v",
|
||||||
|
"-o",
|
||||||
|
"StrictHostKeyChecking=no",
|
||||||
|
"-o",
|
||||||
|
"UserKnownHostsFile=/dev/null",
|
||||||
|
"-o",
|
||||||
|
fmt.Sprintf("CertificateFile=%v", path.Join(yamldir, "user_ca_key-cert.pub")),
|
||||||
|
"-p",
|
||||||
|
piperport,
|
||||||
|
"-l",
|
||||||
|
"cert",
|
||||||
|
"-i",
|
||||||
|
path.Join(yamldir, "user_ca_key"),
|
||||||
|
"127.0.0.1",
|
||||||
|
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to ssh to piper, %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer killCmd(c)
|
||||||
|
|
||||||
|
time.Sleep(time.Second) // wait for file flush
|
||||||
|
|
||||||
|
checkSharedFileContent(t, targetfie, randtext)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,32 @@
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"trusted_user_ca_keys": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"trusted_user_ca_keys_data": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ type pipeConfigFrom struct {
|
||||||
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
|
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
|
||||||
AuthorizedKeys listOrString `yaml:"authorized_keys,omitempty"`
|
AuthorizedKeys listOrString `yaml:"authorized_keys,omitempty"`
|
||||||
AuthorizedKeysData listOrString `yaml:"authorized_keys_data,omitempty"`
|
AuthorizedKeysData listOrString `yaml:"authorized_keys_data,omitempty"`
|
||||||
|
TrustedUserCAKeys listOrString `yaml:"trusted_user_ca_keys,omitempty"`
|
||||||
|
TrustedUserCAKeysData listOrString `yaml:"trusted_user_ca_keys_data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type pipeConfigTo struct {
|
type pipeConfigTo struct {
|
||||||
|
|
@ -200,7 +202,7 @@ func (p *plugin) supportedMethods() ([]string, error) {
|
||||||
|
|
||||||
for _, pipe := range config.Pipes {
|
for _, pipe := range config.Pipes {
|
||||||
for _, from := range pipe.From {
|
for _, from := range pipe.From {
|
||||||
if from.AuthorizedKeys.Any() || from.AuthorizedKeysData.Any() {
|
if from.AuthorizedKeys.Any() || from.AuthorizedKeysData.Any() || from.TrustedUserCAKeys.Any() || from.TrustedUserCAKeysData.Any() {
|
||||||
set["publickey"] = true // found authorized_keys, so we support publickey
|
set["publickey"] = true // found authorized_keys, so we support publickey
|
||||||
} else {
|
} else {
|
||||||
set["password"] = true // no authorized_keys, so we support password
|
set["password"] = true // no authorized_keys, so we support password
|
||||||
|
|
@ -287,6 +289,30 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isCert bool
|
||||||
|
var pkcert *ssh.Certificate
|
||||||
|
|
||||||
|
if publicKey != nil {
|
||||||
|
pubKey, err := ssh.ParsePublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pkcert, isCert = pubKey.(*ssh.Certificate)
|
||||||
|
if isCert {
|
||||||
|
// ensure cert is valid first
|
||||||
|
|
||||||
|
if pkcert.CertType != ssh.UserCert {
|
||||||
|
return nil, fmt.Errorf("only user certificates are supported, cert type: %v", pkcert.CertType)
|
||||||
|
}
|
||||||
|
|
||||||
|
certChecker := ssh.CertChecker{}
|
||||||
|
if err := certChecker.CheckCert(conn.User(), pkcert); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, pipe := range config.Pipes {
|
for _, pipe := range config.Pipes {
|
||||||
for _, from := range pipe.From {
|
for _, from := range pipe.From {
|
||||||
matched := from.Username == user
|
matched := from.Username == user
|
||||||
|
|
@ -316,6 +342,26 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
|
||||||
return p.createUpstream(conn, pipe.To, password)
|
return p.createUpstream(conn, pipe.To, password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isCert {
|
||||||
|
rest, err := p.loadFileOrDecodeMany(from.TrustedUserCAKeys, from.TrustedUserCAKeysData, map[string]string{
|
||||||
|
"DOWNSTREAM_USER": user,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var trustedca ssh.PublicKey
|
||||||
|
for len(rest) > 0 {
|
||||||
|
trustedca, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if subtle.ConstantTimeCompare(trustedca.Marshal(), pkcert.SignatureKey.Marshal()) == 1 {
|
||||||
|
return p.createUpstream(conn, pipe.To, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
rest, err := p.loadFileOrDecodeMany(from.AuthorizedKeys, from.AuthorizedKeysData, map[string]string{
|
rest, err := p.loadFileOrDecodeMany(from.AuthorizedKeys, from.AuthorizedKeysData, map[string]string{
|
||||||
"DOWNSTREAM_USER": user,
|
"DOWNSTREAM_USER": user,
|
||||||
})
|
})
|
||||||
|
|
@ -335,6 +381,8 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("no matching pipe for username [%v] found", user)
|
return nil, fmt.Errorf("no matching pipe for username [%v] found", user)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue