Skip to content
This repository was archived by the owner on Mar 12, 2025. It is now read-only.

Commit 7ddbcba

Browse files
committed
Initial commit
1 parent 138a17d commit 7ddbcba

25 files changed

Lines changed: 10455 additions & 0 deletions

Dockerfile

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Builder
2+
FROM node:16-bullseye-slim as build-test
3+
4+
WORKDIR /build
5+
6+
COPY . .
7+
8+
RUN apt-get update && \
9+
apt-get install -y \
10+
--no-install-recommends \
11+
make \
12+
ca-certificates && \
13+
make build && \
14+
make pkg && \
15+
useradd -u 10005 proxyuser && \
16+
tail -n 1 /etc/passwd > /etc/passwd.scratch
17+
18+
FROM scratch as runtime
19+
20+
WORKDIR /app
21+
22+
COPY --from=build-test /build/index ./resolver
23+
COPY --from=build-test /etc/ssl /etc/ssl
24+
COPY --from=build-test /etc/passwd.scratch /etc/passwd
25+
26+
ENV PATH="${PATH}:/app"
27+
28+
USER 10005
29+
30+
CMD ["resolver"]

Makefile

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
.PHONY: build test pkg
2+
3+
NODE_VERSION := 16
4+
5+
all: build test pkg
6+
7+
help:
8+
@echo "Builds and compiles a static binary"
9+
@echo "The following command are available"
10+
@echo "- build: runs npm install and pkg"
11+
@echo "- test: runs test suite"
12+
@echo "- pkg: packages the project into a static binary"
13+
14+
build:
15+
@npm install
16+
17+
test:
18+
( NODE_ENV="test" ASK_ENABLED="true" npm run test || exit 1)
19+
20+
pkg:
21+
@./node_modules/.bin/pkg -t node$(value NODE_VERSION)-linuxstatic-x64 index.js
22+
23+
image:
24+
@docker \
25+
build \
26+
--rm \
27+
conduit \
28+
.

blacklist/index.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const configuration = require('../configuration/config')
2+
3+
/**
4+
*
5+
* @param {string} domain
6+
* @returns {boolean}
7+
*/
8+
const checkIfDomainIsBlacklisted = (domain) => {
9+
if(!domain) {
10+
return false
11+
}
12+
for(const blockedHost of configuration.resolver.blacklist) {
13+
if(domain.length === blockedHost.length) {
14+
return blockedHost === domain
15+
} else {
16+
if(domain.endsWith(`.${blockedHost}`)) {
17+
return true
18+
}
19+
}
20+
}
21+
return false
22+
}
23+
24+
module.exports = {
25+
checkIfDomainIsBlacklisted
26+
}

cache/redis.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const Redis = require("ioredis");
2+
const logger = require("../logging/log");
3+
const NodeCache = require("node-cache");
4+
const configuration = require("../configuration/config");
5+
const { default: Redlock } = require("redlock");
6+
const { redis } = require("../configuration/config");
7+
8+
const cacheTtl = configuration.cache.ttl;
9+
10+
const localCache = new NodeCache({
11+
stdTTL: cacheTtl,
12+
checkperiod: cacheTtl
13+
});
14+
15+
const redisClient = new Redis(configuration.redis.url);
16+
const redlockClient = new Redis(configuration.redis.url);
17+
18+
const redlock = new Redlock(
19+
[redlockClient],
20+
{
21+
driftFactor: 0.01,
22+
retryCount: 100000,
23+
retryDelay: 5000,
24+
retryJitter: 200,
25+
automaticExtensionThreshold: 500
26+
}
27+
);
28+
29+
async function checkCache(hostname) {
30+
let memCached = await localCache.get(hostname);
31+
if (memCached === undefined) {
32+
try {
33+
let redisCached = await redisClient.get(hostname);
34+
if (redisCached === null) {
35+
return false
36+
} else {
37+
// Populate local in-memory cache from Redis
38+
localCache.set(hostname, redisCached);
39+
let cachedResult = JSON.parse(redisCached);
40+
return cachedResult;
41+
}
42+
} catch (err) {
43+
logger.error("Could not check Redis cache", err)
44+
}
45+
} else {
46+
return JSON.parse(memCached);
47+
}
48+
}
49+
50+
async function updateCache(hostname, content) {
51+
let contentObject = JSON.stringify(content);
52+
localCache.set(hostname, contentObject);
53+
try {
54+
await redisClient.set(hostname, contentObject);
55+
await redisClient.expire(hostname, cacheTtl);
56+
} catch (err) {
57+
logger.error("Error adding item to cache", err);
58+
}
59+
}
60+
61+
62+
async function rateLimitIncr(key, amount, timeForAmount) {
63+
const keyTtl = await redisClient.ttl(key)
64+
logger.debug(`rateLimitIncr: ${key} has ttl ${keyTtl}`)
65+
var times = await redisClient.incr(key)
66+
if(!keyTtl || keyTtl < 0) {
67+
logger.info(`rateLimitIncr: setting TTL for ${key} = ${timeForAmount} seconds`)
68+
await redisClient.expire(key, timeForAmount)
69+
}
70+
71+
return {
72+
times,
73+
periodInSeconds: timeForAmount,
74+
keyTtl,
75+
surpassed: times > amount
76+
}
77+
}
78+
79+
module.exports = {
80+
updateCache,
81+
checkCache,
82+
redlockClient,
83+
redlock,
84+
rateLimitIncr
85+
}

caddy/index.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
const logger = require('../logging/log');
2+
const { rateLimitIncr, checkCache, updateCache } = require("../cache/redis");
3+
const { getDomainOfRequestFromGet, stripSubdomainsFromHost } = require('../utils')
4+
const { resolveEns } = require("../ens/ens");
5+
const configuration = require('../configuration/config');
6+
const {checkIfDomainIsBlacklisted} = require('../blacklist')
7+
const {blockedForLegalReasons} = require('../expressErrors')
8+
const caddy = async (req, res) => {
9+
let ensDomain = getDomainOfRequestFromGet(req);
10+
if (!ensDomain) {
11+
res.status(422);
12+
res.end();
13+
return
14+
}
15+
if(checkIfDomainIsBlacklisted(ensDomain)) {
16+
blockedForLegalReasons(res)
17+
return
18+
}
19+
const parentDomain = stripSubdomainsFromHost(ensDomain)
20+
let isCached = await checkCache(ensDomain);
21+
if (isCached === false) {
22+
if (parentDomain && configuration.ask.rate.enabled) {
23+
logger.info(`caddy: checking for ${parentDomain} rate limit`)
24+
const rateLimit = await rateLimitIncr(`rateLimit/caddy/${parentDomain}`, configuration.ask.rate.limit, configuration.ask.rate.period)
25+
if(rateLimit.surpassed) {
26+
logger.info(`caddy: rate limit exceeded for ${parentDomain} in query for ${ensDomain}, rate limit expires in ${rateLimit.keyTtl} seconds`)
27+
res.status(422)
28+
res.end()
29+
return;
30+
}
31+
} else if (configuration.ask.rate.enabled) {
32+
logger.error(`caddy: ignoring rate limit check, ${ensDomain} does not have a valid parent domain`)
33+
}
34+
let location = await resolveEns(ensDomain);
35+
await updateCache(ensDomain, location);
36+
switch (location.codec) {
37+
case 422:
38+
res.status(422);
39+
res.end();
40+
break;
41+
default:
42+
res.send(location);
43+
res.status(200);
44+
res.end();
45+
break;
46+
}
47+
} else {
48+
switch (isCached.codec) {
49+
case "url":
50+
res.status(422);
51+
res.end();
52+
break;
53+
case 422:
54+
res.status(422);
55+
res.end();
56+
break;
57+
default:
58+
res.send(isCached);
59+
res.status(200);
60+
res.end();
61+
break;
62+
}
63+
}
64+
}
65+
66+
module.exports = caddy

chart/.helmignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Patterns to ignore when building packages.
2+
# This supports shell glob matching, relative path matching, and
3+
# negation (prefixed with !). Only one pattern per line.
4+
.DS_Store
5+
# Common VCS dirs
6+
.git/
7+
.gitignore
8+
.bzr/
9+
.bzrignore
10+
.hg/
11+
.hgignore
12+
.svn/
13+
# Common backup files
14+
*.swp
15+
*.bak
16+
*.tmp
17+
*.orig
18+
*~
19+
# Various IDEs
20+
.project
21+
.idea/
22+
*.tmproj
23+
.vscode/

chart/Chart.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
apiVersion: v2
2+
name: conduit
3+
description: Proxy for ENS domains and distributed storage backends
4+
type: application
5+
version: 0.1.0
6+
appVersion: "1.0.0"

chart/templates/deployment.yaml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
apiVersion: apps/v1
2+
kind: Deployment
3+
metadata:
4+
name: {{ .Values.name }}
5+
labels:
6+
app: {{ .Values.name }}
7+
annotations:
8+
seccomp.security.alpha.kubernetes.io/pod: "runtime/default"
9+
namespace: {{ .Values.namespace }}
10+
spec:
11+
replicas: {{ .Values.replicaCount }}
12+
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
13+
selector:
14+
matchLabels:
15+
app: {{ .Values.name }}
16+
strategy:
17+
type: RollingUpdate
18+
rollingUpdate:
19+
maxUnavailable: {{ .Values.maxUnavailable }}
20+
template:
21+
metadata:
22+
labels:
23+
app: {{ .Values.name }}
24+
spec:
25+
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
26+
{{- if not .Values.local }}
27+
{{- if .Values.nodeSelector }}
28+
nodeSelector:
29+
{{ toYaml .Values.nodeSelector | indent 8 }}
30+
{{- end }}
31+
{{- if .Values.affinity }}
32+
affinity:
33+
{{ toYaml .Values.affinity | indent 8 }}
34+
{{- end }}
35+
{{- end }}
36+
{{- if .Values.imagePullSecrets -}}
37+
{{- with .Values.imagePullSecrets }}
38+
imagePullSecrets:
39+
{{- toYaml . | nindent 8 }}
40+
{{- end }}
41+
{{- end }}
42+
containers:
43+
- name: {{ .Values.name }}
44+
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
45+
imagePullPolicy: {{ .Values.image.pullPolicy }}
46+
securityContext:
47+
runAsNonRoot: true
48+
runAsUser: {{ .Values.securityContext.runAsUser }}
49+
runAsGroup: {{ .Values.securityContext.runAsGroup }}
50+
allowPrivilegeEscalation: false
51+
readOnlyRootFilesystem: true
52+
capabilities:
53+
drop:
54+
- ALL
55+
resources:
56+
{{ toYaml .Values.resources | indent 12 }}
57+
ports:
58+
{{ toYaml .Values.ports | indent 12 }}
59+
env:
60+
{{ toYaml .Values.env | indent 12 }}
61+
{{- if .Values.livenessProbe }}
62+
livenessProbe:
63+
{{ toYaml .Values.livenessProbe | indent 12 -}}
64+
{{ end }}
65+
{{- if .Values.readinessProbe }}
66+
readinessProbe:
67+
{{ toYaml .Values.readinessProbe | indent 12 -}}
68+
{{ end }}

chart/templates/hpa.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{{- if .Values.autoscaling.enabled }}
2+
apiVersion: autoscaling/v2beta1
3+
kind: HorizontalPodAutoscaler
4+
metadata:
5+
name: {{ .Values.name }}
6+
labels:
7+
app: {{ .Values.name }}
8+
spec:
9+
scaleTargetRef:
10+
apiVersion: apps/v1
11+
kind: Deployment
12+
name: {{ .Values.name }}
13+
minReplicas: {{ .Values.autoscaling.minReplicas }}
14+
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
15+
metrics:
16+
{{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
17+
- type: Resource
18+
resource:
19+
name: memory
20+
targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
21+
{{- end }}
22+
{{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
23+
- type: Resource
24+
resource:
25+
name: cpu
26+
targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
27+
{{- end }}
28+
{{- end }}

0 commit comments

Comments
 (0)