-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[improve][doc] Improve TLS encryption #17808
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
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
10b26be
improve TLS encryption
momo-jun 1cc0135
fix review comments
momo-jun 6f1fc82
preview fix
momo-jun a577b2d
add file and syntax for page redirection
momo-jun c5b8edc
update the process to create PEM certs
momo-jun 8e9ebbb
add more description for mTLS
momo-jun cbcbfa7
Merge branch 'master' into TLS-encryption
momo-jun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,334 +1,11 @@ | ||
| --- | ||
| id: cookbooks-encryption | ||
| title: Configure end-to-end encryption | ||
| sidebar_label: "Configure end-to-end encryption" | ||
| title: Pulsar Encryption | ||
| sidebar_label: "Encryption" | ||
| --- | ||
|
|
||
| ````mdx-code-block | ||
| import Tabs from '@theme/Tabs'; | ||
| import TabItem from '@theme/TabItem'; | ||
| ```` | ||
| import {Redirect} from '@docusaurus/router'; | ||
|
|
||
| [Pulsar encryption](security-encryption.md) allows clients to encrypt messages at producers and decrypt messages at consumers. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| * Pulsar Java/Python/C++/Node.js client 2.7.1 or later versions. | ||
| * Pulsar Go client 0.6.0 or later versions. | ||
|
|
||
| ## Configure end-to-end encryption | ||
|
|
||
| 1. Create both public and private key pairs. | ||
| * ECDSA(for Java and Go clients) | ||
| ```shell | ||
| openssl ecparam -name secp521r1 -genkey -param_enc explicit -out test_ecdsa_privkey.pem | ||
| openssl ec -in test_ecdsa_privkey.pem -pubout -outform pem -out test_ecdsa_pubkey.pem | ||
| ``` | ||
|
|
||
| * RSA (for Python, C++ and Node.js clients) | ||
| ```shell | ||
| openssl genrsa -out test_rsa_privkey.pem 2048 | ||
| openssl rsa -in test_rsa_privkey.pem -pubout -outform pkcs8 -out test_rsa_pubkey.pem | ||
| ``` | ||
|
|
||
| 2. Configure a `CryptoKeyReader` on producers, consumers or readers. | ||
|
|
||
| ````mdx-code-block | ||
| <Tabs groupId="lang-choice" | ||
| defaultValue="Java" | ||
| values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"},{"label":"C++","value":"C++"},{"label":"Go","value":"Go"},{"label":"Node.js","value":"Node.js"}]}> | ||
| <TabItem value="Java"> | ||
|
|
||
| ```java | ||
| PulsarClient pulsarClient = PulsarClient.builder().serviceUrl("pulsar://localhost:6650").build(); | ||
| String topic = "persistent://my-tenant/my-ns/my-topic"; | ||
| // RawFileKeyReader is just an example implementation that's not provided by Pulsar | ||
| CryptoKeyReader keyReader = new RawFileKeyReader("test_ecdsa_pubkey.pem", "test_ecdsa_privkey.pem"); | ||
|
|
||
| Producer<byte[]> producer = pulsarClient.newProducer() | ||
| .topic(topic) | ||
| .cryptoKeyReader(keyReader) | ||
| .addEncryptionKey("myappkey") | ||
| .create(); | ||
|
|
||
| Consumer<byte[]> consumer = pulsarClient.newConsumer() | ||
| .topic(topic) | ||
| .subscriptionName("my-subscriber-name") | ||
| .cryptoKeyReader(keyReader) | ||
| .subscribe(); | ||
|
|
||
| Reader<byte[]> reader = pulsarClient.newReader() | ||
| .topic(topic) | ||
| .startMessageId(MessageId.earliest) | ||
| .cryptoKeyReader(keyReader) | ||
| .create(); | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Python"> | ||
|
|
||
| ```python | ||
| from pulsar import Client, CryptoKeyReader | ||
|
|
||
| client = Client('pulsar://localhost:6650') | ||
| topic = 'my-topic' | ||
| # CryptoKeyReader is a built-in implementation that reads public key and private key from files | ||
| key_reader = CryptoKeyReader('test_rsa_pubkey.pem', 'test_rsa_privkey.pem') | ||
|
|
||
| producer = client.create_producer( | ||
| topic=topic, | ||
| encryption_key='myappkey', | ||
| crypto_key_reader=key_reader | ||
| ) | ||
|
|
||
| consumer = client.subscribe( | ||
| topic=topic, | ||
| subscription_name='my-subscriber-name', | ||
| crypto_key_reader=key_reader | ||
| ) | ||
|
|
||
| reader = client.create_reader( | ||
| topic=topic, | ||
| start_message_id=MessageId.earliest, | ||
| crypto_key_reader=key_reader | ||
| ) | ||
|
|
||
| client.close() | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="C++"> | ||
|
|
||
| ```cpp | ||
| Client client("pulsar://localhost:6650"); | ||
| std::string topic = "persistent://my-tenant/my-ns/my-topic"; | ||
| // DefaultCryptoKeyReader is a built-in implementation that reads public key and private key from files | ||
| auto keyReader = std::make_shared<DefaultCryptoKeyReader>("test_rsa_pubkey.pem", "test_rsa_privkey.pem"); | ||
|
|
||
| Producer producer; | ||
| ProducerConfiguration producerConf; | ||
| producerConf.setCryptoKeyReader(keyReader); | ||
| producerConf.addEncryptionKey("myappkey"); | ||
| client.createProducer(topic, producerConf, producer); | ||
|
|
||
| Consumer consumer; | ||
| ConsumerConfiguration consumerConf; | ||
| consumerConf.setCryptoKeyReader(keyReader); | ||
| client.subscribe(topic, "my-subscriber-name", consumerConf, consumer); | ||
|
|
||
| Reader reader; | ||
| ReaderConfiguration readerConf; | ||
| readerConf.setCryptoKeyReader(keyReader); | ||
| client.createReader(topic, MessageId::earliest(), readerConf, reader); | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Go"> | ||
|
|
||
| ```go | ||
| client, err := pulsar.NewClient(pulsar.ClientOptions{ | ||
| URL: "pulsar://localhost:6650", | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
|
|
||
| defer client.Close() | ||
|
|
||
| topic := "persistent://my-tenant/my-ns/my-topic" | ||
| keyReader := crypto.NewFileKeyReader("test_ecdsa_pubkey.pem", "test_ecdsa_privkey.pem") | ||
| producer, err := client.CreateProducer(pulsar.ProducerOptions{ | ||
| Topic: topic, | ||
| Encryption: &pulsar.ProducerEncryptionInfo{ | ||
| KeyReader: keyReader, | ||
| Keys: []string{"myappkey"}, | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| defer producer.Close() | ||
|
|
||
| consumer, err := client.Subscribe(pulsar.ConsumerOptions{ | ||
| Topic: topic, | ||
| SubscriptionName: "my-subscriber-name", | ||
| Decryption: &pulsar.MessageDecryptionInfo{ | ||
| KeyReader: keyReader, | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| defer consumer.Close() | ||
|
|
||
| reader, err := client.CreateReader(pulsar.ReaderOptions{ | ||
| Topic: topic, | ||
| Decryption: &pulsar.MessageDecryptionInfo{ | ||
| KeyReader: keyReader, | ||
| }, | ||
| }) | ||
| if err != nil { | ||
| log.Fatal(err) | ||
| } | ||
| defer reader.Close() | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Node.js"> | ||
|
|
||
| ```javascript | ||
| const Pulsar = require('pulsar-client'); | ||
|
|
||
| const topic = 'persistent://my-tenant/my-ns/my-topic'; | ||
|
|
||
| (async () => { | ||
| // Create a client | ||
| const client = new Pulsar.Client({ | ||
| serviceUrl: 'pulsar://localhost:6650', | ||
| operationTimeoutSeconds: 30, | ||
| }); | ||
|
|
||
| // Create a producer | ||
| const producer = await client.createProducer({ | ||
| topic: topic, | ||
| sendTimeoutMs: 30000, | ||
| batchingEnabled: true, | ||
| publicKeyPath: "test_rsa_pubkey.pem", | ||
| encryptionKey: "encryption-key" | ||
| }); | ||
|
|
||
| // Create a consumer | ||
| const consumer = await client.subscribe({ | ||
| topic: topic, | ||
| subscription: 'my-subscriber-name', | ||
| subscriptionType: 'Shared', | ||
| ackTimeoutMs: 10000, | ||
| privateKeyPath: "test_rsa_privkey.pem" | ||
| }); | ||
| await consumer.close(); | ||
| await producer.close(); | ||
| await client.close(); | ||
| })(); | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
| ```` | ||
|
|
||
| 3. Optional: customize the `CryptoKeyReader` implementation. | ||
|
|
||
| ````mdx-code-block | ||
| <Tabs groupId="lang-choice" | ||
| defaultValue="Java" | ||
| values={[{"label":"Java","value":"Java"},{"label":"Python","value":"Python"},{"label":"C++","value":"C++"},{"label":"Go","value":"Go"},{"label":"Node.js","value":"Node.js"}]}> | ||
| <TabItem value="Java"> | ||
|
|
||
| ```java | ||
| class RawFileKeyReader implements CryptoKeyReader { | ||
|
|
||
| String publicKeyFile = ""; | ||
| String privateKeyFile = ""; | ||
|
|
||
| RawFileKeyReader(String pubKeyFile, String privKeyFile) { | ||
| publicKeyFile = pubKeyFile; | ||
| privateKeyFile = privKeyFile; | ||
| } | ||
|
|
||
| @Override | ||
| public EncryptionKeyInfo getPublicKey(String keyName, Map<String, String> keyMeta) { | ||
| EncryptionKeyInfo keyInfo = new EncryptionKeyInfo(); | ||
| try { | ||
| keyInfo.setKey(Files.readAllBytes(Paths.get(publicKeyFile))); | ||
| } catch (IOException e) { | ||
| System.out.println("ERROR: Failed to read public key from file " + publicKeyFile); | ||
| e.printStackTrace(); | ||
| } | ||
| return keyInfo; | ||
| } | ||
|
|
||
| @Override | ||
| public EncryptionKeyInfo getPrivateKey(String keyName, Map<String, String> keyMeta) { | ||
| EncryptionKeyInfo keyInfo = new EncryptionKeyInfo(); | ||
| try { | ||
| keyInfo.setKey(Files.readAllBytes(Paths.get(privateKeyFile))); | ||
| } catch (IOException e) { | ||
| System.out.println("ERROR: Failed to read private key from file " + privateKeyFile); | ||
| e.printStackTrace(); | ||
| } | ||
| return keyInfo; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Python"> | ||
|
|
||
| Currently, customizing the `CryptoKeyReader` implementation is not supported in Python. However, you can use the default implementation by specifying the path of the private key and public keys. | ||
|
|
||
| </TabItem> | ||
| <TabItem value="C++"> | ||
|
|
||
| ```cpp | ||
| class CustomCryptoKeyReader : public CryptoKeyReader { | ||
| public: | ||
| Result getPublicKey(const std::string& keyName, std::map<std::string, std::string>& metadata, | ||
| EncryptionKeyInfo& encKeyInfo) const override { | ||
| // TODO | ||
| return ResultOk; | ||
| } | ||
|
|
||
| Result getPrivateKey(const std::string& keyName, std::map<std::string, std::string>& metadata, | ||
| EncryptionKeyInfo& encKeyInfo) const override { | ||
| // TODO | ||
| return ResultOk; | ||
| } | ||
| }; | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Go"> | ||
|
|
||
| ```go | ||
| type CustomKeyReader struct { | ||
| publicKeyPath string | ||
| privateKeyPath string | ||
| } | ||
|
|
||
| func (c *CustomKeyReader) PublicKey(keyName string, keyMeta map[string]string) (*EncryptionKeyInfo, error) { | ||
| keyInfo := &EncryptionKeyInfo{} | ||
| // TODO | ||
| return keyInfo, nil | ||
| } | ||
|
|
||
| // PrivateKey read private key from the given path | ||
| func (c *CustomKeyReader) PrivateKey(keyName string, keyMeta map[string]string) (*EncryptionKeyInfo, error) { | ||
| keyInfo := &EncryptionKeyInfo{} | ||
| // TODO | ||
| return keyInfo, nil | ||
| } | ||
| ``` | ||
|
|
||
| </TabItem> | ||
| <TabItem value="Node.js"> | ||
|
|
||
| Currently, customizing the `CryptoKeyReader` implementation is not supported in Python. However, you can use the default implementation by specifying the path of the private key and public keys. | ||
|
|
||
| </TabItem> | ||
| </Tabs> | ||
| ```` | ||
|
|
||
| ## Encrypt a message with multiple keys | ||
|
|
||
| :::note | ||
|
|
||
| This is only available for Java clients. | ||
|
|
||
| ::: | ||
|
|
||
| You can encrypt a message with more than one key. Producers add all such keys to the config and consumers can decrypt the message as long as they have access to at least one of the keys. Any one of the keys used for encrypting the message is sufficient to decrypt the message. | ||
|
|
||
| For example, encrypt the messages using 2 keys (`myapp.messagekey1` and `myapp.messagekey2`): | ||
|
|
||
| ```java | ||
| PulsarClient.newProducer().addEncryptionKey("myapp.messagekey1").addEncryptionKey("myapp.messagekey2"); | ||
| ``` | ||
| <Redirect to="security-encryption#get-started" /> | ||
| ```` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.