-
Notifications
You must be signed in to change notification settings - Fork 59
docs: add auto-tls example #194
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,3 +14,4 @@ playwright-report | |
| .parcel-cache | ||
| .envrc | ||
| .tool-versions | ||
| db | ||
17 changes: 17 additions & 0 deletions
17
examples/js-libp2p-example-auto-tls/.github/pull_request_template.md
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 |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # ⚠️ IMPORTANT ⚠️ | ||
|
|
||
| # Please do not create a Pull Request for this repository | ||
|
|
||
| The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync. | ||
|
|
||
| Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead. | ||
|
|
||
| ## Contributing | ||
|
|
||
| Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**. | ||
|
|
||
| 1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) | ||
| 2. Create your Feature Branch (`git checkout -b feature/amazing-example`) | ||
| 3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`) | ||
| 4. Push to the Branch (`git push origin feature/amazing-example`) | ||
| 5. Open a Pull Request |
19 changes: 19 additions & 0 deletions
19
examples/js-libp2p-example-auto-tls/.github/workflows/sync.yml
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 |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| name: pull | ||
|
|
||
| on: | ||
| workflow_dispatch | ||
|
|
||
| jobs: | ||
| sync: | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - uses: actions/checkout@v2 | ||
| - name: Pull from another repository | ||
| uses: ipfs-examples/actions-pull-directory-from-repo@main | ||
| with: | ||
| source-repo: libp2p/js-libp2p-examples | ||
| source-folder-path: examples/${{ github.event.repository.name }} | ||
| source-branch: main | ||
| target-branch: main | ||
| git-username: github-actions | ||
| git-email: github-actions@github.com |
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 |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| This project is dual licensed under MIT and Apache-2.0. | ||
|
|
||
| MIT: https://www.opensource.org/licenses/mit | ||
| Apache-2.0: https://www.apache.org/licenses/license-2.0 |
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 |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at | ||
|
|
||
| http://www.apache.org/licenses/LICENSE-2.0 | ||
|
|
||
| Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. |
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 |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| The MIT License (MIT) | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in | ||
| all copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| THE SOFTWARE. |
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 |
|---|---|---|
| @@ -0,0 +1,295 @@ | ||
| # @libp2p/example-auto-tls | ||
|
|
||
| [](http://libp2p.io/) | ||
| [](https://discuss.libp2p.io) | ||
| [](https://codecov.io/gh/libp2p/js-libp2p-examples) | ||
| [](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain) | ||
|
|
||
| > How to get a TLS certificate automatically | ||
|
|
||
| [Interplanetary Shipyard](https://ipshipyard.com/) operates a public-good DNS | ||
| service that will answer [Acme DNS-01 Challenges](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) | ||
| on behalf of any Internet user. | ||
|
|
||
| This means that we can use a service such as [Let's Encrypt](https://letsencrypt.org/) | ||
| to generate certificates we can use to upgrade our WebSocket transport listeners | ||
| to a Secure WebSocket version automatically. | ||
|
|
||
| ## AutoTLS flow | ||
|
|
||
| The steps for obtaining a TLS certificate are: | ||
|
|
||
| 1. Have a publicly routable listening address | ||
| 2. Claim the `*.<peerID>.libp2p.direct` domain name by `POST`ing a message to [register.libp2p.direct](https://github.com/ipshipyard/p2p-forge?tab=readme-ov-file#submitting-challenge-records) | ||
| 3. Contact an ACME provider to perform the [DNS-01](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) challenge and generate a certificate | ||
| 4. Add HTTPS listeners to any relevant transports | ||
|
|
||
|
|
||
| ## Setup | ||
|
|
||
| 1. Clone this repo then install the dependencies of this example with `npm install` | ||
|
|
||
| ## Instructions | ||
|
|
||
| The requirements for using `libp2p.direct` are: | ||
|
|
||
| 1. A publicly routable socket address | ||
| 2. A WebSocket or TCP listener | ||
| 3. The [identify](https://www.npmjs.com/package/@libp2p/identify) protocol | ||
|
|
||
| ACME services normally have quite restrictive rate limits, so we'll configure a | ||
| a persistent datastore and a keychain so we can reuse any generated | ||
| certificates, their private keys and the PeerID it's tied to. | ||
|
|
||
| Let's configure the relevant modules: | ||
|
|
||
| ```js | ||
| import { noise } from '@chainsafe/libp2p-noise' | ||
| import { yamux } from '@chainsafe/libp2p-yamux' | ||
| import { webSockets } from '@libp2p/websockets' | ||
| import { createLibp2p } from 'libp2p' | ||
| import { autoTLS } from '@libp2p/auto-tls' | ||
| import { identify, identifyPush } from '@libp2p/identify' | ||
| import { keychain } from '@libp2p/keychain' | ||
| import { LevelDatastore } from 'datastore-level' | ||
| import { loadOrCreateSelfKey } from '@libp2p/config' | ||
|
|
||
| const datastore = new LevelDatastore('./db') | ||
| await datastore.open() | ||
|
|
||
| const privateKey = await loadOrCreateSelfKey(datastore) | ||
|
|
||
| const libp2p = await createLibp2p({ | ||
| datastore, | ||
| privateKey, | ||
| addresses: { | ||
| listen: [ | ||
| '/ip4/0.0.0.0/tcp/0/ws', | ||
| '/ip6/::/tcp/0/ws' | ||
| ] | ||
| }, | ||
| transports: [ | ||
| webSockets() | ||
| ], | ||
| connectionEncrypters: [ | ||
| noise() | ||
| ], | ||
| streamMuxers: [ | ||
| yamux() | ||
| ], | ||
| services: { | ||
| autoTLS: autoTLS(), | ||
| identify: identify(), | ||
| identifyPush: identifyPush(), | ||
| keychain: keychain() | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| > [!TIP] | ||
| > If you want to experiment without hitting ACME service rate limits, you can | ||
| > set the `acmeDirectory` to a staging address, though be aware that any | ||
| > certificates generated will be self-signed: | ||
| > | ||
| > ```TypeScript | ||
| > const libp2p = await createLibp2p({ | ||
| > // other config | ||
| > services: { | ||
| > autoTLS: autoTLS({ | ||
| > acmeDirectory: 'https://acme-staging-v02.api.letsencrypt.org/directory' | ||
| > }), | ||
| > // other config | ||
| > } | ||
| > }) | ||
| > ``` | ||
|
|
||
| ## Getting a publicly routable address | ||
|
|
||
| If we start the node now, we'll notice that nothing happens. The reason is that | ||
| we don't have a publicly routable address. | ||
|
|
||
| ### Manual configuration | ||
|
|
||
| If you know you have a publicly routable address, for example if you are | ||
| deploying your app on a server or you have manually configured port forwarding | ||
| on your router then you can add your transport addresses to the `appendAnnounce` | ||
| config key: | ||
|
|
||
| > [!IMPORTANT] | ||
| > Multiaddrs added to `announce` or `appendAnnounce` will automatically be | ||
| > verified as publicly dialable where possible, though note that the domain name | ||
| > allocated by `libp2p.direct` will still need to be verified via AutoNAT or | ||
| > auto-confirmation - see the next section on confirming dialable addresses for | ||
| > more | ||
|
|
||
| ```js | ||
| const libp2p = await createLibp2p({ | ||
| addresses: { | ||
| appendAnnounce: [ | ||
| '/ip4/123.123.123.123/tcp/1234/ws' | ||
| ], | ||
| // other config | ||
| }, | ||
| // other config | ||
| }) | ||
| ``` | ||
|
|
||
| ### Automatic configuration via UPnP | ||
|
|
||
| If you a home user and your router supports configuring port forwarding via | ||
| [UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play), you can use the | ||
| [@libp2p/upnp-nat](https://www.npmjs.com/package/@libp2p/upnp-nat) module to | ||
| automatically configure port forwarding for IPv4 and IPv6 networks: | ||
|
|
||
| > [!IMPORTANT] | ||
| > This will only work if your router supports configuring port forwarding via | ||
| > UPnP and also has it enabled. | ||
| > | ||
| > ISP provided routers sometimes do not and most ship with UPnP disabled by | ||
| > default - please check your router documentation for more information | ||
|
|
||
| ```TypeScript | ||
| import { uPnPNAT } from '@libp2p/upnp-nat' | ||
|
|
||
| const libp2p = await createLibp2p({ | ||
| services: { | ||
| upnp: uPnPNAT() | ||
| // other config | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| ### Confirming dialable addresses | ||
|
|
||
| Designing distributed systems typically involves trying to trust other system | ||
| components as little as possible. Systems with a lot of trust tend to not be | ||
| [Byztantine fault](https://en.wikipedia.org/wiki/Byzantine_fault)-tolerant. | ||
|
|
||
| So far we've introduced two components that we are implicitly trusting. One is | ||
| `libp2p.direct` - we trust that it will configure the DNS records to answer the | ||
| ACME DNS-01 challenge correctly, and we trust that the external address reported | ||
| by our UPnP router is correct. | ||
|
|
||
| By default libp2p will not broadcast any public address until it has been | ||
| confirmed to be dialable. | ||
|
|
||
| We can skip this and explicitly trust `libp2p.direct` and our router by | ||
| auto-confirming the DNS mapping and the public IP address: | ||
|
|
||
| ```TypeScript | ||
| import { uPnPNAT } from '@libp2p/upnp-nat' | ||
|
|
||
| const libp2p = await createLibp2p({ | ||
| services: { | ||
| autoTLS: autoTLS({ | ||
| // automatically mark *.<peerID>.libp2p.direct as routable | ||
| autoConfirmAddress: true | ||
| }), | ||
| upnp: uPnPNAT({ | ||
| // automatically mark any detected socket address as routable | ||
| autoConfirmAddress: true | ||
| }) | ||
| // other config | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| To not trust these actors and instead require confirmation from multiple peers | ||
| in different network segments that the addresses are, in fact dialable, we need | ||
| to configure [@libp2p/autonat](https://www.npmjs.com/package/@libp2p/autonat). | ||
|
|
||
| #### AutoNAT | ||
|
|
||
| This requires a few more system components. The rough flow here is: | ||
|
|
||
| 1. Acquire network peers that speak the `/libp2p/autonat/1.0.0` protocol | ||
| 2. Ask peers from a range of networks to dial us back on a specific address | ||
| 3. Mark the address as reachable/not reachable after enough responses are received | ||
|
|
||
| To find network peers we need a peer routing system such as [@libp2p/kad-dht](https://www.npmjs.com/package/@libp2p/kad-dht) (One day we may be able to use a [lightweight HTTP alternative](https://github.com/ipfs/specs/pull/476) | ||
| but that day is not today). | ||
|
|
||
| So far we've only configured a WebSocket listener, but if we use the Amino | ||
| flavour of KAD-DHT (e.g. the public IPFS network), we have to be aware of | ||
| [the spread of supported transports](https://probelab.io/ipfs/amino/#dht-transport-distribution). | ||
|
|
||
| Because the WebSockets transport is not common, we need to add the [@libp2p/tcp](https://www.npmjs.com/package/@libp2p/tcp) transport to increase our likelihood of being able to | ||
| communicate with network peers. | ||
|
|
||
| Finally we need to also use the [@libp2p/bootstrap](https://www.npmjs.com/package/@libp2p/bootstrap) | ||
| module to connect to an initial set of peers that will let us start to fill our | ||
| routing table and perform queries: | ||
|
|
||
| ```js | ||
| import { autoNAT } from '@libp2p/autonat' | ||
| import { bootstrap } from '@libp2p/bootstrap' | ||
| import { kadDHT, removePrivateAddressesMapper } from '@libp2p/kad-dht' | ||
| import { tcp } from '@libp2p/tcp' | ||
|
|
||
| const libp2p = await createLibp2p({ | ||
| // other config | ||
| transports: [ | ||
| // other config | ||
| tcp() | ||
| ], | ||
| services: { | ||
| autoNAT: autoNAT(), | ||
| aminoDHT: kadDHT({ | ||
| protocol: '/ipfs/kad/1.0.0', | ||
| peerInfoMapper: removePrivateAddressesMapper | ||
| }), | ||
| bootstrap: bootstrap({ | ||
| list: [ | ||
| '/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN', | ||
| '/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb', | ||
| '/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt', | ||
| '/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8', | ||
| '/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ' | ||
| ] | ||
| }) | ||
| // other config | ||
| } | ||
| }) | ||
| ``` | ||
|
|
||
| If you are running on your own network which has better support for varied | ||
| transports you may not need to add `@libp2p/tcp`. | ||
|
|
||
| ## Putting it all together | ||
|
|
||
| Phew, well done making it this far. | ||
|
|
||
| If you are happy trusting the IP address assigned by your ISP and router, and | ||
| that `libp2p.direct` has configured DNS for you correctly, you can run the | ||
| working example in [./auto-confirm.js](./auto-confirm.js). | ||
|
|
||
| If you want to go the full trust-free route, please see the example in | ||
| [./trust-free.js](./trust-free.js). | ||
|
|
||
| After a little while you should see multiaddr(s) that include the | ||
| [Server name indication](https://en.wikipedia.org/wiki/Server_Name_Indication) | ||
| tuple: | ||
|
|
||
| ```console | ||
| $ node ./trust-free.js | ||
| /ip4/[ip-address]/tcp/[port]/tls/sni/ip-address.base32-peer-id.libp2p.direct/ws/p2p/12D3Foo | ||
| ``` | ||
|
|
||
| ## Need help? | ||
|
|
||
| - Read the [js-libp2p documentation](https://github.com/libp2p/js-libp2p/tree/main/doc) | ||
| - Check out the [js-libp2p API docs](https://libp2p.github.io/js-libp2p/) | ||
| - Check out the [general libp2p documentation](https://docs.libp2p.io) for tips, how-tos and more | ||
| - Read the [libp2p specs](https://github.com/libp2p/specs) | ||
| - Ask a question on the [js-libp2p discussion board](https://github.com/libp2p/js-libp2p/discussions) | ||
|
|
||
| ## License | ||
|
|
||
| Licensed under either of | ||
|
|
||
| - Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| - MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>) | ||
|
|
||
| ## Contribution | ||
|
|
||
| Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. | ||
Oops, something went wrong.
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.
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.
How did you decide which services to keep for each of these sections?
For example, shouldn't we also include identify here which is necessary for most of the other services to work?
Uh oh!
There was an error while loading. Please reload this page.
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 was meant to be an additive thing, not replacing the existing config blocks.
It says
// other configeverywhere but I guess that's not obvious enough - do you think using a formatteddiffinstead of ajscode block would be clearer?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.
Yeah I think adding a diff block would be better, runnable code is included, so should be a problem