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

feat: Allow master configuration for ssh key crypto system #10072

Merged
merged 17 commits into from
Oct 24, 2024

Conversation

gt2345
Copy link
Contributor

@gt2345 gt2345 commented Oct 16, 2024

Ticket

MD-475

Description

Add master config option to specify which algorithm to use to generate ssh key.

        security:
          ssh:
            key_type: "RSA" | "ECDSA" | "ED25519"

Default is "RSA" for backward compatibility.

This PR also removes the -p --passphrase option for det shell start, since it does not make sense and it's been broken since at least 01/2023. More discussion here.

Test Plan

Start master with one of the crypto system
Run det shell start
There should be a message similar to

shell (id: ab3068f6-f252-43f8-9b90-c3adf0985bda) is ready.                      
Warning: Permanently added 'ab3068f6-f252-43f8-9b90-c3adf0985bda' (ECDSA) to the list of known hosts.

if "ECDSA" is the crypto system.

Checklist

  • Changes have been manually QA'd
  • New features have been approved by the corresponding PM
  • User-facing API changes have the "User-facing API Change" label
  • Release notes have been added as a separate file under docs/release-notes/
    See Release Note for details.
  • Licenses have been included for new code which was copied and/or modified from any external code

@gt2345 gt2345 requested review from a team as code owners October 16, 2024 22:09
@cla-bot cla-bot bot added the cla-signed label Oct 16, 2024
Copy link

codecov bot commented Oct 16, 2024

Codecov Report

Attention: Patch coverage is 67.27273% with 18 lines in your changes missing coverage. Please review.

Project coverage is 54.45%. Comparing base (7d6a1a7) to head (1690bb7).
Report is 39 commits behind head on main.

Files with missing lines Patch % Lines
master/pkg/ssh/ssh.go 75.00% 11 Missing ⚠️
master/internal/config/config.go 37.50% 5 Missing ⚠️
master/internal/api_shell.go 0.00% 1 Missing ⚠️
master/internal/core.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #10072      +/-   ##
==========================================
+ Coverage   54.42%   54.45%   +0.02%     
==========================================
  Files        1262     1262              
  Lines      158886   158910      +24     
  Branches     3630     3630              
==========================================
+ Hits        86472    86527      +55     
+ Misses      72280    72249      -31     
  Partials      134      134              
Flag Coverage Δ
backend 45.58% <67.27%> (+0.07%) ⬆️
harness 72.75% <ø> (+<0.01%) ⬆️
web 53.95% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
harness/determined/cli/shell.py 23.36% <ø> (-0.06%) ⬇️
master/internal/experiment.go 33.70% <100.00%> (ø)
master/pkg/tasks/task.go 62.30% <ø> (ø)
master/internal/api_shell.go 33.79% <0.00%> (+1.76%) ⬆️
master/internal/core.go 6.23% <0.00%> (ø)
master/internal/config/config.go 58.06% <37.50%> (-0.57%) ⬇️
master/pkg/ssh/ssh.go 79.62% <75.00%> (+12.96%) ⬆️

... and 4 files with indirect coverage changes

Copy link

netlify bot commented Oct 16, 2024

Deploy Preview for determined-ui canceled.

Name Link
🔨 Latest commit 1690bb7
🔍 Latest deploy log https://app.netlify.com/sites/determined-ui/deploys/67198fcfb7137d0008cd8827

@gt2345 gt2345 changed the title Gt/475 ssh key alg feat: Allow master configuration for ssh key crypto system Oct 16, 2024
@determined-ci determined-ci requested a review from a team October 16, 2024 22:37
@determined-ci determined-ci added the documentation Improvements or additions to documentation label Oct 16, 2024
@gt2345 gt2345 requested a review from a team as a code owner October 17, 2024 15:08
@gt2345 gt2345 requested a review from ioga October 17, 2024 15:55
Copy link
Contributor

@ioga ioga left a comment

Choose a reason for hiding this comment

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

there's a typo in PR / commit template: EC25519 instead of ED25519. everything looks ok in the code though.


func generateECDSAKey() (PrivateAndPublicKeys, error) {
var generatedKeys PrivateAndPublicKeys
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
Copy link
Contributor

Choose a reason for hiding this comment

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

Just as a note: I don't think there were any requests to make the curve size configurable, and P256 is the NIST recommendation, so this is fine. But one day we may have to update it or make configurable.

Comment on lines 5 to 6
- Master Configuration: Add support for crypto system configuration for ssh connection. ``security
-> crypto_system`` now accepts ``RSA``, ``ECDSA`` or ``ED25519``.
Copy link
Contributor

Choose a reason for hiding this comment

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

Looking through the release notes, we usually use period instead of ->, i.e. security.crypto_system.

Suggested change
- Master Configuration: Add support for crypto system configuration for ssh connection. ``security
-> crypto_system`` now accepts ``RSA``, ``ECDSA`` or ``ED25519``.
- Master Configuration: Add support for crypto system configuration for ssh connection. ``security.crypto_system`` now accepts ``RSA``, ``ECDSA`` or ``ED25519``.

@@ -108,7 +117,8 @@ func DefaultConfig() *Config {
Group: "root",
},
SSH: SSHConfig{
RsaKeySize: 1024,
RsaKeySize: 1024,
CryptoSystem: RSACryptoSystem,
Copy link
Contributor

Choose a reason for hiding this comment

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

I was under impression we wanted to switch to ECDSA by default, because it's faster and compliant with FIPS140-2 requirements. I'd strongly suggest we do the switch, while leaving an option for the users to revert back to RSA if they really want.

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 wonder if @mackrorysd or @rb-determined-ai want to say anything about this?

Copy link
Contributor

Choose a reason for hiding this comment

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

It is faster than reasonably-sized RSA keys. I never liked the 1024 key size choice when we made it years ago, because key-per-task took too long if you used 2048 keys. The argument I made was "let's not do one key per task then" And the argument made back to me was "1024 is fine if the tasks are not long lived" and I lost.

Certainly I don't think 1024 rsa keys is a good default by any measure.

I personally would default to ed25519 keys, but I think Ilia is right about what most customers would prefer.

Copy link
Contributor

Choose a reason for hiding this comment

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

Wait, actually, I do vote ed25519 because that's what ssh-keygen defaults to.

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 don't know too much about ssh keys, but it looks like newer algorithms are better in terms of the balance of security and speed, so I put ed25519 down as default for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

ed25519 is not in FIPS140, so the category of users who cares about that will have to be aware of it and change the config. ECDSA is still plenty fast and fit most requirements, which feels like the best fit for the default option.

Copy link
Member

Choose a reason for hiding this comment

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

Don't have a strong opinion here myself, but to repeat some information added by @dannysauer:

FWIW: FIPS 186-5, Digital Signature Standard includes ED25519 as of its official publication date Feb last year.
https://csrc.nist.gov/pubs/fips/186-5/final
This is helpful, because ECDSA depends on having a good random number generator, which is not always the case.

Copy link
Member

Choose a reason for hiding this comment

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

I would side with ED25519 here the more I've read about it. It seems to be the technically superior solution, it is getting accepted in other federal standard, etc.

@gt2345 gt2345 requested a review from a team as a code owner October 17, 2024 19:26
@@ -452,7 +462,8 @@ type SecurityConfig struct {

// SSHConfig is the configuration setting for SSH.
type SSHConfig struct {
RsaKeySize int `json:"rsa_key_size"`
RsaKeySize int `json:"rsa_key_size"`
CryptoSystem string `json:"crypto_system"`
Copy link
Contributor

Choose a reason for hiding this comment

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

crypto_system seems like an unexpected phrase for what it means. How about key_type?

For example, man ssh-keygen says:

The type of key to be generated is specified with the -t option. If invoked without any arguments, ssh-keygen will generate an Ed25519 key.

@determined-ci determined-ci requested a review from a team October 18, 2024 20:50
Comment on lines 46 to 47
// RSACryptoSystem uses RSA.
RSACryptoSystem = "RSA"
Copy link
Contributor

Choose a reason for hiding this comment

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

Replace 'CryptoSystem' with 'KeyType' or something.

Comment on lines 5 to 6
- Master Configuration: Add support for crypto system configuration for ssh connection.
``security.key_type`` now accepts ``RSA``, ``ECDSA`` or ``ED25519``.
Copy link
Contributor

Choose a reason for hiding this comment

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

Worth mentioning the transition to ED25519 keys by default, and I'd also mention that ED25519 keys are faster and more secure than the old default, as well as being the default key type for ssh-keygen.

Mentioning that we're using more secure keys makes us look good, and mentioning that we're adopting the same default as ssh-keygen helps to reduce support burden by making the change sound like obvious rather than arbitrary.

}

func TestSSHKeyGenerate(t *testing.T) {
keys, err := GenerateKey(config.SSHConfig{KeyType: config.RSACryptoSystem, RsaKeySize: 1024})
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: for the unit test, can you use a keysize of 512?

It's absurdly small for security purposes, but it should exercise the codepaths just fine.

@determined-ci determined-ci requested a review from a team October 22, 2024 15:49
@gt2345 gt2345 requested review from corban-beaird and removed request for a team October 22, 2024 15:54
Copy link

@molinamelendezj molinamelendezj left a comment

Choose a reason for hiding this comment

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

stamped from infra

@gt2345 gt2345 requested review from maxrussell and removed request for corban-beaird October 22, 2024 17:30
Copy link
Contributor

@stoksc stoksc left a comment

Choose a reason for hiding this comment

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

lgtm, only have non-blocking feedback (i.e., i'd appreciate it being addressed but don't explicitly expect to re-review)

@@ -118,7 +118,7 @@ func setupAPITest(t *testing.T, pgdb *db.PgDB,
TaskContainerDefaults: model.TaskContainerDefaultsConfig{},
ResourceConfig: *config.DefaultResourceConfig(),
},
taskSpec: &tasks.TaskSpec{SSHRsaSize: 1024},
taskSpec: &tasks.TaskSpec{SSHConfig: config.SSHConfig{RsaKeySize: 1024, KeyType: "RSA"}},
Copy link
Contributor

Choose a reason for hiding this comment

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

as a follow up: can/should we default to ed25519?

if t.KeyType != RSAKeyType && t.KeyType != ECDSAKeyType && t.KeyType != ED25519KeyType {
errs = append(errs, errors.New("Crypto system must be one of 'RSA', 'ECDSA' or 'ED25519'"))
}
if t.KeyType != RSAKeyType {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we flip this and put this rsa specific stuff in an if t.KeyType == RSAKeyType block? written like this, it's easy for someone to add a check and the bottom of this func and not realize it's just for RSA.

Copy link
Contributor

Choose a reason for hiding this comment

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

+1.

Comment on lines 96 to 101
generatedKeys = PrivateAndPublicKeys{
PrivateKey: pem.EncodeToMemory(block),
PublicKey: sshlib.MarshalAuthorizedKey(publicKey),
}

return generatedKeys, nil
Copy link
Contributor

Choose a reason for hiding this comment

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

nit

Suggested change
generatedKeys = PrivateAndPublicKeys{
PrivateKey: pem.EncodeToMemory(block),
PublicKey: sshlib.MarshalAuthorizedKey(publicKey),
}
return generatedKeys, nil
return PrivateAndPublicKeys{
PrivateKey: pem.EncodeToMemory(block),
PublicKey: sshlib.MarshalAuthorizedKey(publicKey),
}, nil

}

func generateED25519Key() (PrivateAndPublicKeys, error) {
var generatedKeys PrivateAndPublicKeys
Copy link
Contributor

Choose a reason for hiding this comment

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

same nit here

Copy link
Contributor

Choose a reason for hiding this comment

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

The nit being that this could skip being declared at all? If so, I agree. I'd rather see

return PrivateAndPublicKeys{}, errors.Wrap(err, "unable to generate ED25519 private key")

and then no generatedKeys ever declared.

Copy link
Contributor

Choose a reason for hiding this comment

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

agree

}

func TestSSHKeyGenerate(t *testing.T) {
keys, err := GenerateKey(config.SSHConfig{KeyType: config.RSAKeyType, RsaKeySize: 512})
Copy link
Contributor

Choose a reason for hiding this comment

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

this test is so short it doesn't matter, but usually i'd recommend splitting a test like/using subtests with t.Run()/ using table driven tests this up since it really is three separate tests. it's easier for whoever has to fix one of them if it fails to read just the one test that failed... but i also doubt this test will ever fail. this comment is just FYI.

Copy link
Contributor

Choose a reason for hiding this comment

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

Big +1 to table-driven tests and subtests.

@stoksc stoksc removed the request for review from maxrussell October 23, 2024 20:51
@stoksc
Copy link
Contributor

stoksc commented Oct 23, 2024

i traded with max so he has time to review a pr of mine.

Comment on lines 46 to 51
// RSAKeyType uses RSA.
RSAKeyType = "RSA"
// ECDSAKeyType uses ECDSA.
ECDSAKeyType = "ECDSA"
// ED25519KeyType uses ED25519.
ED25519KeyType = "ED25519"
Copy link
Contributor

Choose a reason for hiding this comment

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

Idiomatically, these names should be KeyTypeRSA, KeyTypeECDSA, and KeyTypeED25519. A good example of this in the stdlib is net/http, where all the methods and statuses are named this way.

if t.KeyType != RSAKeyType && t.KeyType != ECDSAKeyType && t.KeyType != ED25519KeyType {
errs = append(errs, errors.New("Crypto system must be one of 'RSA', 'ECDSA' or 'ED25519'"))
}
if t.KeyType != RSAKeyType {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1.

}

func generateED25519Key() (PrivateAndPublicKeys, error) {
var generatedKeys PrivateAndPublicKeys
Copy link
Contributor

Choose a reason for hiding this comment

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

The nit being that this could skip being declared at all? If so, I agree. I'd rather see

return PrivateAndPublicKeys{}, errors.Wrap(err, "unable to generate ED25519 private key")

and then no generatedKeys ever declared.

}

func TestSSHKeyGenerate(t *testing.T) {
keys, err := GenerateKey(config.SSHConfig{KeyType: config.RSAKeyType, RsaKeySize: 512})
Copy link
Contributor

Choose a reason for hiding this comment

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

Big +1 to table-driven tests and subtests.

@gt2345 gt2345 requested a review from maxrussell October 23, 2024 23:05
Copy link
Contributor

@maxrussell maxrussell left a comment

Choose a reason for hiding this comment

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

LGTM!

@gt2345 gt2345 merged commit fd9cd8a into main Oct 24, 2024
82 of 95 checks passed
@gt2345 gt2345 deleted the gt/475-ssh-key-alg branch October 24, 2024 00:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cla-signed documentation Improvements or additions to documentation
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants