commit
6ae40bb6bd
|
@ -8,6 +8,14 @@ updates:
|
||||||
open-pull-requests-limit: 10
|
open-pull-requests-limit: 10
|
||||||
assignees:
|
assignees:
|
||||||
- kwypchlo
|
- kwypchlo
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: "/packages/dnslink-api"
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
time: "10:00"
|
||||||
|
open-pull-requests-limit: 10
|
||||||
|
assignees:
|
||||||
|
- kwypchlo
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
directory: "/packages/handshake-api"
|
directory: "/packages/handshake-api"
|
||||||
schedule:
|
schedule:
|
||||||
|
|
|
@ -26,3 +26,6 @@ jobs:
|
||||||
|
|
||||||
- name: "Static code analysis: dashboard"
|
- name: "Static code analysis: dashboard"
|
||||||
run: yarn workspace dashboard prettier --check .
|
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:
|
depends_on:
|
||||||
- sia
|
- sia
|
||||||
- handshake-api
|
- handshake-api
|
||||||
|
- dnslink-api
|
||||||
- website
|
- website
|
||||||
|
|
||||||
website:
|
website:
|
||||||
|
@ -142,6 +143,19 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- handshake
|
- 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:
|
health-check:
|
||||||
build:
|
build:
|
||||||
context: ./packages/health-check
|
context: ./packages/health-check
|
||||||
|
|
|
@ -7,7 +7,19 @@
|
||||||
on_demand
|
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.
|
# 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;
|
return 461;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# redirect to dnslookup endpoint
|
||||||
|
error_page 462 = @dnslink_lookup;
|
||||||
|
if ($http_dnslink_lookup) {
|
||||||
|
return 462;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
include /etc/nginx/conf.d/include/cors;
|
include /etc/nginx/conf.d/include/cors;
|
||||||
|
|
||||||
|
@ -452,6 +458,31 @@ server {
|
||||||
proxy_pass http://siad/skynet/skylink/$skylink_v1$path$is_args$args;
|
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 {
|
location @base32_subdomain {
|
||||||
include /etc/nginx/conf.d/include/proxy-buffer;
|
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