commit
6ae40bb6bd
|
@ -8,6 +8,14 @@ updates:
|
|||
open-pull-requests-limit: 10
|
||||
assignees:
|
||||
- kwypchlo
|
||||
- package-ecosystem: npm
|
||||
directory: "/packages/dnslink-api"
|
||||
schedule:
|
||||
interval: weekly
|
||||
time: "10:00"
|
||||
open-pull-requests-limit: 10
|
||||
assignees:
|
||||
- kwypchlo
|
||||
- package-ecosystem: npm
|
||||
directory: "/packages/handshake-api"
|
||||
schedule:
|
||||
|
|
|
@ -26,3 +26,6 @@ jobs:
|
|||
|
||||
- name: "Static code analysis: dashboard"
|
||||
run: yarn workspace dashboard prettier --check .
|
||||
|
||||
- name: "Static code analysis: dnslink-api"
|
||||
run: yarn workspace dnslink-api prettier --check .
|
||||
|
|
|
@ -81,6 +81,7 @@ services:
|
|||
depends_on:
|
||||
- sia
|
||||
- handshake-api
|
||||
- dnslink-api
|
||||
- website
|
||||
|
||||
website:
|
||||
|
@ -142,6 +143,19 @@ services:
|
|||
depends_on:
|
||||
- handshake
|
||||
|
||||
dnslink-api:
|
||||
build:
|
||||
context: ./packages/dnslink-api
|
||||
dockerfile: Dockerfile
|
||||
container_name: dnslink-api
|
||||
restart: unless-stopped
|
||||
logging: *default-logging
|
||||
networks:
|
||||
shared:
|
||||
ipv4_address: 10.10.10.55
|
||||
expose:
|
||||
- 3100
|
||||
|
||||
health-check:
|
||||
build:
|
||||
context: ./packages/health-check
|
||||
|
|
|
@ -7,7 +7,19 @@
|
|||
on_demand
|
||||
}
|
||||
|
||||
reverse_proxy nginx:80
|
||||
reverse_proxy nginx:80 {
|
||||
# add Dnslink-Lookup header so nginx knows that the request comes from a domain
|
||||
# outside of our certificate string and should perform a dnslink lookup
|
||||
header_up Dnslink-Lookup true
|
||||
}
|
||||
}
|
||||
|
||||
:80 {
|
||||
reverse_proxy nginx:80 {
|
||||
# add Dnslink-Lookup header so nginx knows that the request comes from a domain
|
||||
# outside of our certificate string and should perform a dnslink lookup
|
||||
header_up Dnslink-Lookup true
|
||||
}
|
||||
}
|
||||
|
||||
# Make sure you have SSL_CERTIFICATE_STRING specified in .env file because you need it to fetch correct certificates.
|
||||
|
|
|
@ -88,6 +88,12 @@ server {
|
|||
return 461;
|
||||
}
|
||||
|
||||
# redirect to dnslookup endpoint
|
||||
error_page 462 = @dnslink_lookup;
|
||||
if ($http_dnslink_lookup) {
|
||||
return 462;
|
||||
}
|
||||
|
||||
location / {
|
||||
include /etc/nginx/conf.d/include/cors;
|
||||
|
||||
|
@ -452,6 +458,31 @@ server {
|
|||
proxy_pass http://siad/skynet/skylink/$skylink_v1$path$is_args$args;
|
||||
}
|
||||
|
||||
location @dnslink_lookup {
|
||||
include /etc/nginx/conf.d/include/proxy-buffer;
|
||||
|
||||
set $dnslink '';
|
||||
|
||||
rewrite_by_lua_block {
|
||||
local http = require("socket.http")
|
||||
local ok, statusCode, headers, statusText = http.request {
|
||||
url = "http://dnslink-api:3100/dnslink/" .. ngx.var.host
|
||||
}
|
||||
|
||||
if statusCode == ngx.HTTP_OK then
|
||||
ngx.var.dnslink = headers["skynet-skylink"]
|
||||
else
|
||||
ngx.status = statusCode
|
||||
ngx.header["content-type"] = "text/plain"
|
||||
ngx.say(headers["dnslink-error"])
|
||||
ngx.exit(statusCode)
|
||||
end
|
||||
}
|
||||
|
||||
proxy_set_header Dnslink-Lookup "";
|
||||
proxy_pass http://127.0.0.1/$dnslink/$request_uri;
|
||||
}
|
||||
|
||||
location @base32_subdomain {
|
||||
include /etc/nginx/conf.d/include/proxy-buffer;
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/package.json
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"printWidth": 120
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
FROM node:16.3.0-alpine
|
||||
|
||||
WORKDIR /usr/app
|
||||
|
||||
COPY package.json .
|
||||
RUN yarn --no-lockfile
|
||||
COPY src/* src/
|
||||
|
||||
EXPOSE 3100
|
||||
CMD node src
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"name": "dnslink-api",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"dependencies": {
|
||||
"express": "^4.17.1",
|
||||
"is-valid-domain": "^0.0.20",
|
||||
"node-cache": "^5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
const dns = require("dns");
|
||||
const express = require("express");
|
||||
const NodeCache = require("node-cache");
|
||||
const isValidDomain = require("is-valid-domain");
|
||||
|
||||
const host = process.env.DNSLINK_API_HOSTNAME || "0.0.0.0";
|
||||
const port = Number(process.env.DNSLINK_API_PORT) || 3100;
|
||||
const cacheTTL = Number(process.env.DNSLINK_API_CACHE_TTL) || 300; // default to 5 minutes
|
||||
|
||||
const server = express();
|
||||
const cache = new NodeCache({ stdTTL: cacheTTL });
|
||||
|
||||
const dnslinkNamespace = "skynet-ns";
|
||||
const dnslinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/.+$`);
|
||||
const dnslinkSkylinkRegExp = new RegExp(`^dnslink=/${dnslinkNamespace}/([a-zA-Z0-9_-]{46}|[a-z0-9]{55})`);
|
||||
const hint = `valid example: dnslink=/${dnslinkNamespace}/3ACpC9Umme41zlWUgMQh1fw0sNwgWwyfDDhRQ9Sppz9hjQ`;
|
||||
|
||||
server.get("/dnslink/:name", async (req, res) => {
|
||||
const success = (skylink) => res.set("Skynet-Skylink", skylink).send(skylink);
|
||||
const failure = (message) => res.status(400).set("Dnslink-Error", message).send(message);
|
||||
|
||||
if (!isValidDomain(req.params.name)) {
|
||||
return failure(`"${req.params.name}" is not a valid domain`);
|
||||
}
|
||||
|
||||
if (cache.has(req.params.name)) {
|
||||
return success(cache.get(req.params.name));
|
||||
}
|
||||
|
||||
const lookup = `_dnslink.${req.params.name}`;
|
||||
|
||||
dns.resolveTxt(lookup, (error, records) => {
|
||||
if (error) {
|
||||
if (error.code === "ENOTFOUND") {
|
||||
return failure(`ENOTFOUND: ${lookup} TXT record doesn't exist`);
|
||||
}
|
||||
|
||||
if (error.code === "ENODATA") {
|
||||
return failure(`ENODATA: ${lookup} dns lookup returned no data`);
|
||||
}
|
||||
|
||||
return failure(`Failed to fetch ${lookup} TXT record: ${error.message}`);
|
||||
}
|
||||
|
||||
if (records.length === 0) {
|
||||
return failure(`No TXT record found for ${lookup}`);
|
||||
}
|
||||
|
||||
const dnslinks = records.flat().filter((record) => dnslinkRegExp.test(record));
|
||||
|
||||
if (dnslinks.length === 0) {
|
||||
return failure(`TXT records for ${lookup} found but none of them contained valid skynet dnslink - ${hint}`);
|
||||
}
|
||||
|
||||
if (dnslinks.length > 1) {
|
||||
return failure(`Multiple TXT records with valid skynet dnslink found for ${lookup}, only one allowed`);
|
||||
}
|
||||
|
||||
const [dnslink] = dnslinks;
|
||||
const matchSkylink = dnslink.match(dnslinkSkylinkRegExp);
|
||||
|
||||
if (!matchSkylink) {
|
||||
return failure(`TXT record with skynet dnslink for ${lookup} contains invalid skylink - ${hint}`);
|
||||
}
|
||||
|
||||
const skylink = matchSkylink[1];
|
||||
|
||||
cache.set(req.params.name, skylink);
|
||||
|
||||
console.log(`${req.params.name} => ${skylink}`);
|
||||
|
||||
return success(skylink);
|
||||
});
|
||||
});
|
||||
|
||||
server.listen(port, host, (error) => {
|
||||
if (error) throw error;
|
||||
|
||||
console.info(`Server listening at http://${host}:${port}`);
|
||||
});
|
Reference in New Issue