dappnode / DAppNode

General repository of the project dappnode
GNU General Public License v3.0
588 stars 104 forks source link

DAppNode on HTTPS 🔒 #157

Closed dapplion closed 3 years ago

dapplion commented 4 years ago

DAppNode on HTTPS

We've got PSL Inclusion (Mozilla Public Suffix List) as DAppNode :smile: https://github.com/publicsuffix/list/pull/912 let's use this great opportunity to empower our users.

:lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock: :lock:

Goals

Part 1: API for certificate issuance

We want to issue certificates for users programmatically https://letsencrypt.org/docs/challenge-types/

We want to use "DNS-01 challenge" so our DACM (DAppNode Association Certificate Manager) can ask for certificates on users' behalf without the user's DAppNode having to ever expose a port. Then, the DACM will send the fresh certificate to the user

Users will "register" to our DACM when they start the DAppNode. The internal NodeJS manager app currently queries the DACM to get a domain to which direct VPN connections. In addition to this, the DACM (NodeJS) app will have an endpoint that returns the certificate files.

The domain structure will be

*.bd45344949c2f047.dyndns.dappnode.io

where the long hex string is derived from a public key that each DAppNode has to prove to own on each request. The domain of each DAppNode's package then will be

geth.bd45344949c2f047.dyndns.dappnode.io
my.geth.bd45344949c2f047.dyndns.dappnode.io
geth.dnp.dappnode.eth.bd45344949c2f047.dyndns.dappnode.io

or some pattern that is UX friendly but prevents collisions given that users can install arbitrary packages. Currently, we rely on ENS and the APM to guarantee name uniqueness.

Part 2: Integrate DAppNode services with certificate

Now, each DAppNode has locally valid certificates for all its internal services.

Develop a network structure fronted-ed with an internal Nginx or Traefic through which all requests to DAppNode packages go (Eth node, owncloud, IPFS, etc). For example, when the user wants to use owncloud they will navigate to https://owncloud.bd45344949c2f047.dyndns.dappnode.io

Part 3: Serve dwebs with certificate

The user types some-dapp.eth to the browser. Then their DAppNode will use the ENS from its own Eth node to resolve the domain to an IPFS hash and serve the content from its own local IPFS node.

Find a UX compromise to be able to serve that IPFS content via HTTPS while retaining the original identity of the website somehow. Note that currently there can not exist valid domains for .eth domains as of now in major browsers.

This part involves active research.

edisinovcic commented 4 years ago

morgoth ➜ ~ dig -t NS dyndns.dappnode.io ; <<>> DiG 9.11.5-P4-5.1+deb10u2-Debian <<>> -t NS dyndns.dappnode.io ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 9659 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;dyndns.dappnode.io. IN NS ;; ANSWER SECTION: dyndns.dappnode.io. 21599 IN NS localhost. ;; Query time: 265 msec ;; SERVER: 10.0.1.1#53(10.0.1.1) ;; WHEN: uto lis 13 11:52:24 CEST 2020 ;; MSG SIZE rcv

DeFiYaco commented 4 years ago

We could either modify this https://github.com/SteveLTN/https-portal to support DNS-01 challenge or we could dockerize this solution: https://victorbush.com/2018/04/lets-encrypt-nginx-dns-docker/

dapplion commented 4 years ago

As a reference, this is the client code performing the IP update to the DynDNS service

Client

async function updateIp(): Promise<string | void> {
  const privateKey = db.dyndnsIdentity.get().privateKey;
  const timestamp = Math.floor(Date.now() / 1000);

  // Using ethers as a raw signer (without '\x19Ethereum Signed Message:\n' prefix) to mimic previous EthCrypto signature
  const wallet = new ethers.Wallet(privateKey);
  const signingKey = new ethers.utils.SigningKey(privateKey);
  const hash = ethers.utils.solidityKeccak256(
    ["string"],
    [timestamp.toString()]
  );
  const signature = ethers.utils.joinSignature(signingKey.signDigest(hash));

const parameters = [
    `address=${wallet.address}`,
    `timestamp=${timestamp}`,
    `sig=${signature}`
  ];
  const url = `${dyndnsHost}/?${parameters.join("&")}`;
  const res = await fetch(url);

  //     'ip': '63.84.220.164',
  //     'domain': '1234abcd1234acbd.dyndns.dappnode.io',
  const bodyData: {ip: string; domain: string} = await res.json();
  return bodyData.domain;
}

https://github.com/dappnode/DNP_DAPPMANAGER/blob/cbd7cbcf3e155f0dd7e9e969ee37b431fb916819/packages/dappmanager/src/modules/dyndns/updateIp.ts#L29:L70

Server

https://github.com/dappnode/dyndns-server/blob/56891e6da3f40202427486911c38807ce537c5f5/api/src/server.js#L35

DPTJKKVH commented 3 years ago

I hope you don't mind if I kindly ask that you also explore the option to only expose the DAppNode within the local network (e.g. 192.168.*.*) in this mode. Even if it is with self signed certificates that need to be trusted on first connection.

I absolutely adore the fact that you have and continue to build easy to use full nodes that are accessible from everywhere. I definitely want to run such a node one day.

However I currently (and for the foreseeable future) share a network with others that won't appreciate if I'd run a publicly accessible server inside our local network. I know isolating the node in its own VLAN would be a solution, however this is currently not possible due to technical limitations.

So all I currently want (and need) is a local full node that I can connect metamask to without vpn, so I don't have to trust and rely on third party services like infura or mew.

I hope this could be made possible with your current plans. Many thanks either way!

dapplion commented 3 years ago

Hi @DPTJKKVH ! Absolutely, I share your concerns. The plan is to not expose anything to the outside by default. You would have to turn on some switch or click a button in the UI for that to happen.

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions!