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(backend): provision private key #638

Merged
merged 14 commits into from
Oct 26, 2022
Merged

Conversation

sabineschaller
Copy link
Member

Changes proposed in this pull request

  • load private key or create one
  • part of backend config

Context

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Documentation added
  • Make sure that all checks pass

@github-actions github-actions bot added pkg: backend Changes in the backend package. type: source Changes business logic labels Sep 26, 2022
wilsonianb
wilsonianb previously approved these changes Sep 26, 2022
@sabineschaller
Copy link
Member Author

Should this PR also add the key to the internal key registry?

packages/backend/src/config/app.ts Outdated Show resolved Hide resolved
packages/backend/src/config/app.ts Outdated Show resolved Hide resolved
packages/backend/src/config/app.ts Outdated Show resolved Hide resolved
if (!fs.existsSync('./tmp')) {
fs.mkdirSync('./tmp')
}
fs.writeFileSync(
Copy link
Contributor

Choose a reason for hiding this comment

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

This will overwrite an existing private.pem if the provided PRIVATE_KEY_PATH couldn't be loaded, which is probably ok 🤔

Copy link
Member Author

Choose a reason for hiding this comment

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

I was wondering that, too. But I don't see a reason to keep the old ones as long as the new one is also associated with this RS. And by associating I mean adding it to the internal key registry. Should this PR also do that?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm leaning towards not overwriting.
I don't like the idea of potentially overwriting a file not intended for for Rafiki, however unlikely such a misconfiguration may be.

Copy link
Contributor

Choose a reason for hiding this comment

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

And by associating I mean adding it to the internal key registry. Should this PR also do that?

I think we should hold off until we decide if we're going to switch the key registry to using JWKS

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm leaning towards not overwriting.
I don't like the idea of potentially overwriting a file not intended for for Rafiki, however unlikely such a misconfiguration may be.

Should we use unique tmp dirs?

Copy link
Member Author

@sabineschaller sabineschaller Oct 12, 2022

Choose a reason for hiding this comment

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

I think we should hold off until we decide if we're going to switch the key registry to using JWKS

I'll create an issue to also create a payment pointer for the Rafiki instance itself that will then be linked to the key, in whatever way and form we end up doing that (probably JWKS endpoint).

packages/backend/src/config/app.ts Outdated Show resolved Hide resolved
@github-actions github-actions bot added the type: tests Testing related label Sep 29, 2022
packages/backend/src/config/app.test.ts Outdated Show resolved Hide resolved
packages/backend/src/config/app.test.ts Outdated Show resolved Hide resolved
packages/backend/.gitignore Outdated Show resolved Hide resolved
packages/backend/src/config/app.test.ts Outdated Show resolved Hide resolved
Comment on lines 43 to 52
expect(key).toBeInstanceOf(crypto.KeyObject)
expect(jwk).toMatchObject({
crv: 'Ed25519',
kty: 'OKP',
d: expect.any(String),
x: expect.any(String)
})
expect(key.export({ format: 'pem', type: 'pkcs8' })).toEqual(
keypair.privateKey.export({ format: 'pem', type: 'pkcs8' })
)
Copy link
Contributor

Choose a reason for hiding this comment

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

https://nodejs.org/api/crypto.html#keyobjectequalsotherkeyobject

Suggested change
expect(key).toBeInstanceOf(crypto.KeyObject)
expect(jwk).toMatchObject({
crv: 'Ed25519',
kty: 'OKP',
d: expect.any(String),
x: expect.any(String)
})
expect(key.export({ format: 'pem', type: 'pkcs8' })).toEqual(
keypair.privateKey.export({ format: 'pem', type: 'pkcs8' })
)
assert.ok(key.equals(keypair.privateKey))
expect(jwk).toEqual(key.export({ format: 'jwk' }))
expect(jwk).toEqual({
crv: 'Ed25519',
kty: 'OKP',
d: expect.any(String),
x: expect.any(String)
})

Copy link
Member Author

Choose a reason for hiding this comment

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

Property 'equals' does not exist on type 'KeyObject'.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member Author

Choose a reason for hiding this comment

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

yes

src/config/app.test.ts:59:21 - error TS2339: Property 'equals' does not exist on type 'KeyObject'.
59       assert.ok(key.equals(keypair.privateKey))
                       ~~~~~~
Found 1 error.

I even made sure to use node v16.18.0.

packages/backend/src/config/app.test.ts Outdated Show resolved Hide resolved
packages/backend/src/config/app.test.ts Outdated Show resolved Hide resolved
Comment on lines +164 to +168
if (!fs.existsSync(TMP_DIR)) {
fs.mkdirSync(TMP_DIR)
}
fs.writeFileSync(
PRIVATE_KEY_FILE,
Copy link
Contributor

Choose a reason for hiding this comment

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

Now I'm wondering if parseOrProvisionKey should only operate with keyPath as opposed to TMP_DIR/PRIVATE_KEY_FILE, to prevent an operator from not realizing that the default PRIVATE_KEY_PATH is being used and not their configured one (even though that is being logged).

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm not sure I understand. PRIVATE_KEY_FILE does include the path.

Copy link
Contributor

Choose a reason for hiding this comment

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

As in parseOrProvisionKey would only read or write (maybe not overwrite) the file at keyPath.
As is, it reads from keyPath but writes to PRIVATE_KEY_FILE.

Copy link
Member Author

Choose a reason for hiding this comment

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

But what if it only has read access and not write access at keyPath?

Copy link
Contributor

@wilsonianb wilsonianb Oct 14, 2022

Choose a reason for hiding this comment

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

I'm imagining an operator running multiple instances of rafiki backend in a k8s cluster and configuring PRIVATE_KEY_FILE as a path to a secret.
In which case, I think we should throw if we can't use (or write to) the configured file (keyPath).
Otherwise you end up with individual instances provisioning their own private keys.

if (!fs.existsSync('./tmp')) {
fs.mkdirSync('./tmp')
}
fs.writeFileSync(
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm leaning towards not overwriting.
I don't like the idea of potentially overwriting a file not intended for for Rafiki, however unlikely such a misconfiguration may be.

mkurapov
mkurapov previously approved these changes Oct 21, 2022
Copy link
Contributor

@mkurapov mkurapov left a comment

Choose a reason for hiding this comment

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

I was able to test successfully, both with existing and new, generated key.

As you two are discussing in the comment, similarly, I do see the benefit of maybe having the parseOrProvisionKey take undefined as a possible argument, and doing the keyPath generation inside the method itself. This way if a defined keyPath is given, we know its's coming from a user-set config, not a default value, in which case we will throw as @wilsonianb mentions

try {
const key = crypto.createPrivateKey(fs.readFileSync(keyFile))
const jwk = key.export({ format: 'jwk' })
if (jwk.crv === 'Ed25519') {
Copy link
Contributor

Choose a reason for hiding this comment

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

It would be nice to have 'Ed25519' coming from a type declaration, but it looks like we are limited to just a string here.

Copy link
Contributor

Choose a reason for hiding this comment

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

@wilsonianb
Copy link
Contributor

Merging/rebasing main should resolve the failing Docker checks

@sabineschaller sabineschaller merged commit 92c89a3 into main Oct 26, 2022
@sabineschaller sabineschaller deleted the s2-key-provisioning branch October 26, 2022 03:28
omertoast pushed a commit that referenced this pull request Oct 28, 2022
* feat(backend): provision private key

* fix(backend): check key curve

* fix(backend): key loading error handling

* feat(backend): store private key in tmp folder

* fix(backend): check if dir exists, move tmp folder into config folder

* refactor(backend): use const string for tmp dir and key file

* test(backend): key provisioning

* test(backend): include further checks

* feat(backend): provision key returns key object and jwk

* test(backend): add Brandon's suggestions

* revert(backend): only return key

* feat(backend): don't overwrite keyfiles

* chore(backend): re-add line in gitignore

* feat(backend): throw if key file cannot be read
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pkg: backend Changes in the backend package. type: source Changes business logic type: tests Testing related
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Self-provisioned keys
3 participants