Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for using CA certs for upstream ssh connection #210

Merged
merged 6 commits into from
Aug 26, 2023

Conversation

mattpker
Copy link
Contributor

@mattpker mattpker commented Aug 24, 2023

Opening this draft as a proposed solution to #204.

This can be used with the following example:

PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) {
	// Load the private key
	signer, err := ioutil.ReadFile("./example")
	if err != nil {
		return nil, fmt.Errorf("unable to read private key: %v", err)
	}

	// Load the public ca certificate
	capublickey, err := ioutil.ReadFile("./example-cert.pub")
	if err != nil {
		return nil, fmt.Errorf("unable to read ca cert: %v", err)
	}

	return &libplugin.Upstream{
		Host:          host,
		Port:          int32(port),
		IgnoreHostKey: true,
		Auth:          libplugin.CreateCACertAuth(signer, capublickey),
	}, nil
},

I may have done the protoc part wrong, I was having trouble getting it to exactly match your output and could not find any specific info in your docs. Looking to get feedback if this is a good solution and if it can be added to sshpiper. Thanks!

@tg123
Copy link
Owner

tg123 commented Aug 24, 2023

any different from PrivateKey?

@mattpker
Copy link
Contributor Author

any different from PrivateKey?

Yes, this is adding support for ssh connections using signed CA certs documented here: https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/deployment_guide/sec-creating_ssh_ca_certificate_signing-keys

@mattpker
Copy link
Contributor Author

Another way to look at it is this is the equivalent of doing this in your ssh config:

Match Host some.example.com
    IdentityFile ~/.ssh/example
    CertificateFile ~/.ssh/example-cert.pub
    User mattpker

~/.ssh/example would be YOUR private key
~/.ssh/example-cert.pub would be the signed CA public cert, that was signed by the ca private key and your public key.

@tg123
Copy link
Owner

tg123 commented Aug 24, 2023

merge to UpstreamPrivateKeyAuth?
add a optional field public key?

@mattpker
Copy link
Contributor Author

mattpker commented Aug 24, 2023

merge to UpstreamPrivateKeyAuth? add a optional field public key?

Something like this?

if a := upstream.GetPrivateKey(); a != nil {
	private, err := ssh.ParsePrivateKey(a.GetPrivateKey())
	if err != nil {
		return nil, err
	}

	if pk, _, _, _, err := ssh.ParseAuthorizedKey(a.GetCaPublicKey()); err == nil {
		private, err := ssh.NewCertSigner(pk.(*ssh.Certificate), private)
		if err != nil {
			return nil, err
		}
	}

	config.Auth = append(config.Auth, ssh.PublicKeys(private))
	auth = append(auth, "privatekey")
}

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

yup

but i still did not understand the purpose of this
https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.12.0:ssh/certs.go;l=253

seems it just check if cert match the publickey of signer?
why not just use privatekey

@mattpker
Copy link
Contributor Author

why not just use privatekey

We can't just use the private key, because the upstream server does not have the matching public key to that private key. Instead it only had the public CA cert.

but i still did not understand the purpose of this
https://cs.opensource.google/go/x/crypto/+/refs/tags/v0.12.0:ssh/certs.go;l=253
seems it just check if cert match the publickey of signer?

Yes it does check if the cert matches the publickey of signer, but it also "returns a Signer that signs with the given Certificate, whose private key is held by signer." This is the the part we need so that we can connect to the server with the signed CA public key.

Tomorrow I am going to work on building an e2e test for this and the suggestion you made for making the CA public cert an optional field to CreatePrivateKeyAuth. Hopefully with a working e2e test it will make more sense.

Thanks for your help so far, really appreciated!

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

Thanks
thing new learnt, never know ssh support certificate

@mattpker
Copy link
Contributor Author

@tg123 I made the change to do it as an optional variable on CreatePrivateKeyAuth. Looking for some early feedback on that while I figure out the e2e tests.

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

Thanks this approach look good to me

@mattpker mattpker marked this pull request as ready for review August 25, 2023 22:35
@mattpker
Copy link
Contributor Author

@tg123 This is now ready for review. I added your suggested error checking and an e2e test for it.

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

the log is too big,
search FAIL: to locate err unable to read ca cert: open /etc/ssh/ssh_host_ed25519_key-cert.pub

2023-08-25T22:41:39.6992292Z �[36;1mtestrunner_1        |�[0m debug1: SSH2_MSG_SERVICE_ACCEPT received
2023-08-25T22:41:39.6992580Z 
2023-08-25T22:41:39.6992870Z �[36;1mtestrunner_1        |�[0m debug1: Authentications that can continue: password
2023-08-25T22:41:39.6993100Z 
2023-08-25T22:41:39.6993349Z �[36;1mtestrunner_1        |�[0m debug1: Next authentication method: password
2023-08-25T22:41:39.6993715Z 
2023-08-25T22:41:40.5963641Z �[36;1mtestrunner_1        |�[0m 
2023-08-25T22:41:40.5964394Z [email protected]'s password: 2023/08/25 22:41:40 got password prompt, sending password
2023-08-25T22:41:40.5971705Z �[36;1mtestrunner_1        |�[0m �[37mDEBU�[0m[0001] downstream 127.0.0.1:41004 (username [client_123]) is sending password auth 
2023-08-25T22:41:40.5972117Z �[36;1mtestrunner_1        |�[0m 
2023-08-25T22:41:40.5992852Z �[36;1mtestrunner_1        |�[0m �[31mERRO�[0m[0002] cannot create upstream for 127.0.0.1:41004 (username [client_123]) with password auth: rpc error: code = Unknown desc = unable to read ca cert: open /etc/ssh/ssh_host_ed25519_key-cert.pub: no such file or directory 
2023-08-25T22:41:40.5993706Z �[36;1mtestrunner_1        |�[0m debug1: Authentications that can continue: password
2023-08-25T22:41:40.5993916Z 
2023-08-25T22:41:40.5994315Z �[36;1mtestrunner_1        |�[0m Permission denied, please try again.
2023-08-25T22:41:40.5994503Z 
2023-08-25T22:41:41.5967506Z �[36;1mtestrunner_1        |�[0m 
2023-08-25T22:41:41.5972249Z [email protected]'s password:     main_test.go:118: failed to open shared file, open /shared/6be58ad1-738f-441f-b333-dd075eadb391: no such file or directory
2023-08-25T22:41:41.5973490Z �[36;1mtestrunner_1        |�[0m     main_test.go:124: failed to read shared file, invalid argument
2023-08-25T22:41:41.5977011Z �[36;1mtestrunner_1        |�[0m     main_test.go:128: shared file content mismatch, expected 0604540a-7333-41d7-8ffe-aff2f56fc4a3, got 
2023-08-25T22:41:41.5981186Z �[36;1mtestrunner_1        |�[0m --- FAIL: TestCa (3.01s)

@mattpker
Copy link
Contributor Author

mattpker commented Aug 25, 2023

Hmm, looks like the GitHub actions test runner could not find /etc/ssh/ssh_host_ed25519_key-cert.pub which is created in the e2e/e2eentry.sh file. Not sure why that didn't work, it works when I run it.

SSHPIPERD_DEBUG=1 docker-compose up --force-recreate --build -d
docker exec -ti e2e-testrunner-1 bash
go test -run TestCa
...
PASS
ok  	github.com/tg123/sshpiper/e2e	3.016s

Any ideas why that wouldn't be working on GitHub Actions?

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

you mean here

ssh-keygen -s cahost/ca -I sshpiper_test -n client_123 /etc/ssh/ssh_host_ed25519_key.pub

seems it is /etc/ssh/ssh_host_ed25519_key-cert.pub in code, should be the same?

@mattpker
Copy link
Contributor Author

Yes, that command should create /etc/ssh/ssh_host_ed25519_key-cert.pub.

SSHPIPERD_DEBUG=1 docker-compose up --force-recreate --build -d
docker exec -ti e2e-testrunner-1 bash
root@9e76d51ba6d6:/src/e2e# ls -lah /etc/ssh
total 32K
drwxr-xr-x 1 root root 4.0K Aug 25 23:02 .
drwxr-xr-x 1 root root 4.0K Aug 25 23:02 ..
-rw-r--r-- 1 root root 1.7K Jul  1  2022 ssh_config
drwxr-xr-x 2 root root 4.0K Jul  1  2022 ssh_config.d
-rw------- 1 root root  411 Aug 25 23:02 ssh_host_ed25519_key
-rw-r--r-- 1 root root  660 Aug 25 23:02 ssh_host_ed25519_key-cert.pub
-rw-r--r-- 1 root root   99 Aug 25 23:02 ssh_host_ed25519_key.pub

See it created the ssh_host_ed25519_key-cert.pub file in that docker. Can you try it on your local machine?

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

seems github merge master before running e2e

do_ca_sign: unable to open "/etc/ssh/ssh_host_ed25519_key.pub": No such file or directory

recent snap change make server key gen self-contained without using ssh-keygen
i believe that is the cause

@tg123
Copy link
Owner

tg123 commented Aug 25, 2023

tested your branch GOOD

tested your branch merge with master BAD

@mattpker
Copy link
Contributor Author

@tg123 My bad I didn't merge with master locally. It should be fixed now.

Copy link
Owner

@tg123 tg123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for sending the missing feature

e2e/docker-compose.yml Outdated Show resolved Hide resolved
@@ -0,0 +1,61 @@
//go:build full || e2e

package main
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any plan to support it in some other plugin?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did it this way because I was just trying to quickly get an e2e test working rather than building a fully working plugin. This should also provide working example code for anyone that would need to get started with this new feature. The plugin that is being working on by us is pretty specific and closed source. If there is any other parts that can be made open source, I will be sure to make another pull request.

@tg123
Copy link
Owner

tg123 commented Aug 26, 2023

let me know when you are ready to merge

@mattpker
Copy link
Contributor Author

Okay fixed your last comment, ready to be merged whenever. Thanks again for all your help!

@tg123 tg123 merged commit 13fcc8c into tg123:master Aug 26, 2023
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants