Skip to content

Commit

Permalink
add support for ssh ca auth (#467)
Browse files Browse the repository at this point in the history
  • Loading branch information
tg123 authored Oct 23, 2024
1 parent a3f6995 commit ed19d55
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 17 deletions.
82 changes: 82 additions & 0 deletions e2e/yaml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ pipes:
username: "user"
ignore_hostkey: true
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) {
Expand Down Expand Up @@ -125,6 +133,43 @@ func TestYaml(t *testing.T) {
); err != nil {
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(
Expand Down Expand Up @@ -155,13 +200,17 @@ func TestYaml(t *testing.T) {

AuthorizedKeys_Simple string
AuthorizedKeys_Catchall string

TrustedUserCAKeys string
}{
KnownHostsKey: base64.StdEncoding.EncodeToString(knownHostsKeyData),
KnownHostsPass: base64.StdEncoding.EncodeToString(knownHostsPassData),
PrivateKey: path.Join(yamldir, "id_rsa"),

AuthorizedKeys_Simple: path.Join(yamldir, "id_rsa_simple.pub"),
AuthorizedKeys_Catchall: path.Join(yamldir, "id_rsa_catchall.pub"),

TrustedUserCAKeys: path.Join(yamldir, "ca_key.pub"),
}); err != nil {
t.Fatalf("Failed to write yaml file %v", err)
}
Expand Down Expand Up @@ -397,4 +446,37 @@ func TestYaml(t *testing.T) {
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)
})
}
28 changes: 27 additions & 1 deletion plugin/yaml/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,33 @@
"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": [
"username"
Expand Down
80 changes: 64 additions & 16 deletions plugin/yaml/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ import (
)

type pipeConfigFrom struct {
Username string `yaml:"username"`
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
AuthorizedKeys listOrString `yaml:"authorized_keys,omitempty"`
AuthorizedKeysData listOrString `yaml:"authorized_keys_data,omitempty"`
Username string `yaml:"username"`
UsernameRegexMatch bool `yaml:"username_regex_match,omitempty"`
AuthorizedKeys listOrString `yaml:"authorized_keys,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 {
Expand Down Expand Up @@ -200,7 +202,7 @@ func (p *plugin) supportedMethods() ([]string, error) {

for _, pipe := range config.Pipes {
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
} else {
set["password"] = true // no authorized_keys, so we support password
Expand Down Expand Up @@ -287,6 +289,30 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
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 _, from := range pipe.From {
matched := from.Username == user
Expand Down Expand Up @@ -316,24 +342,46 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
return p.createUpstream(conn, pipe.To, password)
}

rest, err := p.loadFileOrDecodeMany(from.AuthorizedKeys, from.AuthorizedKeysData, map[string]string{
"DOWNSTREAM_USER": user,
})
if err != nil {
return nil, err
}
if isCert {
rest, err := p.loadFileOrDecodeMany(from.TrustedUserCAKeys, from.TrustedUserCAKeysData, map[string]string{
"DOWNSTREAM_USER": user,
})
if err != nil {
return nil, err
}

var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
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{
"DOWNSTREAM_USER": user,
})
if err != nil {
return nil, err
}

if subtle.ConstantTimeCompare(authedPubkey.Marshal(), publicKey) == 1 {
return p.createUpstream(conn, pipe.To, "")
var authedPubkey ssh.PublicKey
for len(rest) > 0 {
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
if err != nil {
return nil, err
}

if subtle.ConstantTimeCompare(authedPubkey.Marshal(), publicKey) == 1 {
return p.createUpstream(conn, pipe.To, "")
}
}
}

}
}

Expand Down

0 comments on commit ed19d55

Please sign in to comment.