-
Notifications
You must be signed in to change notification settings - Fork 89
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
Conversation
Should this PR also add the key to the internal key registry? |
if (!fs.existsSync('./tmp')) { | ||
fs.mkdirSync('./tmp') | ||
} | ||
fs.writeFileSync( |
There was a problem hiding this comment.
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 🤔
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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).
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' }) | ||
) |
There was a problem hiding this comment.
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
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) | |
}) |
There was a problem hiding this comment.
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'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
😕
https://nodejs.org/dist/latest-v16.x/docs/api/crypto.html#keyobjectequalsotherkeyobject
Is that a typescript build error?
There was a problem hiding this comment.
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.
if (!fs.existsSync(TMP_DIR)) { | ||
fs.mkdirSync(TMP_DIR) | ||
} | ||
fs.writeFileSync( | ||
PRIVATE_KEY_FILE, |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this 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') { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we could eventually do schema validation using:
https://github.com/interledger/open-payments/blob/b125873edb6d33acd40eb304e026e5111ec1dba9/openapi/shared/schemas.yaml#L201-L234
Merging/rebasing main should resolve the failing Docker checks |
* 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
Changes proposed in this pull request
config
Context
Checklist
fixes #number