feat(ci): move to dnscontrol

This commit is contained in:
William Harrison
2024-10-19 14:02:50 +08:00
parent b84a12b074
commit 1e866e01e3
29 changed files with 143 additions and 445 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
/sub-logs/* @is-a-good-dev/helpers
/.github/* @Tweak4141
/scripts/* @Tweak4141
/utils/* @Tweak4141
/.github/workflows/* @Tweak4141 @wdhdev
/dnsconfig.js @Tweak4141 @wdhdev
+32
View File
@@ -0,0 +1,32 @@
name: Publish
on:
push:
branches: [main]
paths:
- "sub-logs/*"
- ".github/workflows/publish.yml"
- "dnsconfig.js"
workflow_dispatch:
concurrency:
group: ${{ github.ref }}-publish
cancel-in-progress: false
jobs:
dns:
name: DNS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate creds.json
run: echo '{"cloudflare":{"TYPE":"CLOUDFLAREAPI","apitoken":"$CLOUDFLARE_API_TOKEN"}}' > ./creds.json
- name: Publish
uses: is-a-dev/dnscontrol-action@main
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.API_KEY }}
with:
args: push
-33
View File
@@ -1,33 +0,0 @@
name: Update Records
on:
push:
branches: [main]
jobs:
validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Get Files
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
sub-logs/**
reserved/**
- name: Install Modules
if: steps.changed-files.outputs.any_changed == 'true'
run: yarn
- name: Publish Records
if: steps.changed-files.outputs.any_changed == 'true'
id: tests
run: yarn add-records
env:
API_KEY: ${{ secrets.API_KEY }}
FILES: ${{ steps.changed-files.outputs.all_changed_files }}
actions_path: ${{ github.workspace }}
+42
View File
@@ -0,0 +1,42 @@
name: Validation
on:
pull_request:
push:
branches: [main]
paths:
- "sub-logs/*"
- ".github/workflows/validation.yml"
- "dnsconfig.js"
workflow_dispatch:
concurrency:
group: ${{ github.ref }}-validation
cancel-in-progress: true
jobs:
dns:
name: DNS
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check
uses: is-a-dev/dnscontrol-action@main
with:
args: check
json:
name: JSON
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: JSON Syntax Check
uses: limitusus/json-syntax-check@v2
with:
pattern: "\\.json$"
env:
BASE: "domains/"
-45
View File
@@ -1,45 +0,0 @@
name: Validation
on: [pull_request]
jobs:
validation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Get Files
id: changed-files
uses: tj-actions/changed-files@v34
with:
files: |
sub-logs/**
reserved/**
- name: Install Modules
if: steps.changed-files.outputs.any_changed == 'true'
run: yarn
- name: Run Tests
if: steps.changed-files.outputs.any_changed == 'true'
id: tests
run: yarn test
env:
FILES: ${{ steps.changed-files.outputs.all_changed_files }}
actions_path: ${{ github.workspace }}
- name: Comment
if: steps.tests.outputs.shouldComment == 'true'
uses: mshick/add-pr-comment@v1
with:
message: |
## Validation Results
| Information Validation | Record Validation |
|-|-|
| ${{steps.tests.outputs.infoMessage}} ${{steps.tests.outputs.infoReason}} | ${{steps.tests.outputs.recordMessage}} ${{steps.tests.outputs.recordInfo}} |
### JSON File Content
```json
${{steps.tests.outputs.jsonData}}
```
repo-token: ${{ secrets.GITHUB_TOKEN }}
proxy-url: https://is-a-good-dev-gh-proxy.talosbot.xyz/
repo-token-user-login: 'github-actions[bot]' # The user.login for temporary GitHub tokens
allow-repeats: false # Default: false
-3
View File
@@ -1,3 +0,0 @@
node_modules/
dist/
package-lock.json
+53
View File
@@ -0,0 +1,53 @@
function getDomainsList(filesPath) {
var result = [];
var files = glob.apply(null, [filesPath, true, ".json"]);
for (var i = 0; i < files.length; i++) {
var name = files[i].split("/").pop().replace(/\.json$/, "");
result.push({ name: name, data: require(files[i]) });
}
return result;
}
var domains = getDomainsList("./sub-logs");
var commit = [];
for (var subdomain in domains) {
var subdomainName = domains[subdomain].name;
var domainData = domains[subdomain].data;
var proxyState = domainData.proxied ? { cloudflare_proxy: "on" } : { cloudflare_proxy: "off" };
// Handle A records
if (domainData.record.A) {
for (var a in domainData.record.A.value) {
commit.push(A(domainData.record.A.name, IP(domainData.record.A.value[a]), proxyState));
}
}
// Handle AAAA records
if (domainData.record.AAAA) {
for (var aaaa in domainData.record.AAAA.value) {
commit.push(AAAA(domainData.record.AAAA.name, domainData.record.AAAA.value[aaaa], proxyState));
}
}
// Handle CNAME records
if (domainData.record.CNAME) {
// Allow CNAME record on root
if (subdomainName === "@") {
commit.push(ALIAS(subdomainName, domainData.record.CNAME + ".", proxyState));
} else {
commit.push(CNAME(domainData.record.CNAME.name, domainData.record.CNAME.value + ".", proxyState));
}
}
// Handle TXT records
if (domainData.record.TXT) {
commit.push(TXT(domainData.record.TXT.name === "@" ? subdomainName : domainData.record.TXT.name + "." + subdomainName, domainData.record.TXT.value));
}
}
// Commit all DNS records
D("is-a-good.dev", NewRegistrar("none"), DnsProvider(NewDnsProvider("cloudflare")), commit);
-35
View File
@@ -1,35 +0,0 @@
const fetch = require('node-fetch');
const getJSON = require('../utils/getJSON.js');
const data = getJSON(process.env.FILES);
const arrayRecordTypes = ["a", "aaaa"];
async function addRecord(type, name, value) {
const response = await fetch(`https://api.is-a-good.dev/api/is-a-good-dev/zones/add?apiKey=${process.env.API_KEY}&type=${type.toUpperCase()}&name=${name}&content=${value}`);
const data = await response.json();
return data;
}
(async () => {
try {
if (!data) return;
const records = Object.keys(data.target);
for (const i in records) {
const recordType = records[i];
const name = data.target[recordType].name;
const value = data.target[recordType].value;
console.log(`Adding record type ${recordType} with values ${name}, ${value}`);
if (arrayRecordTypes.includes(recordType.toLowerCase()) != true) {
let res = await addRecord(recordType, name, value);
return console.log(res);
}
for (const ip of value) {
let res = await addRecord(recordType, name, ip);
console.log(res);
}
}
} catch(e) {
console.log('Failed to add records.');
console.log(e);
}
})();
+1 -1
View File
@@ -9,7 +9,7 @@
"target": {
"A": {
"name": "aniruddh",
"value": "23.94.14.190"
"value": ["23.94.14.190"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "anya",
"value": "87.98.164.155"
"value": ["87.98.164.155"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "arman",
"value": "89.106.200.1"
"value": ["89.106.200.1"]
}
},
+1 -1
View File
@@ -13,7 +13,7 @@
},
"TXT": {
"name": "asuid.avengers-sonarqube",
"name": "asuid",
"value": "2ADF2B3BF4F2D427644C6A908F1607F17C2A1AAE36C1A5E86A6FDFD7B56B2149"
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "ionetty",
"value": "66.94.121.50"
"value": ["66.94.121.50"]
}
},
-15
View File
@@ -1,15 +0,0 @@
{
"owner": {
"username": "michaelbelete",
"email": "it.michael.belete@gmail.com"
},
"target": {
"TXT": {
"name": "michaelbelete",
"value": "vc-domain-verify=michaelbelete.is-a-good.dev,c5ef3d006ace4ff62d3e"
}
},
"proxied": false
}
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "oreki",
"value": "164.132.74.251"
"value": ["164.132.74.251"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "paul",
"value": "192.95.42.75"
"value": ["192.95.42.75"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "Raja",
"value": "43.204.88.80"
"value": ["43.204.88.80"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "rehanplayz",
"value": "198.251.81.131"
"value": ["198.251.81.131"]
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "redirect",
"value": "52.72.49.79"
"value": ["52.72.49.79"]
}
},
+2 -2
View File
@@ -7,11 +7,11 @@
"target": {
"A": {
"name": "themediocre",
"value": "34.132.134.162"
"value": ["34.132.134.162"]
},
"TXT": {
"name": "themediocre",
"name": "@",
"value": "replit-verify=7a770ba4-a691-4406-b81a-e009a792e0ae"
}
},
+1 -1
View File
@@ -11,7 +11,7 @@
},
"TXT": {
"name": "_github-pages-challenge-vuquangthinh.tonywu",
"name": "_github-pages-challenge-vuquangthinh",
"value": "0b9dc3e2752d1dc520c401ce8b7557"
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"target": {
"A": {
"name": "vishal",
"value": "43.205.87.144"
"value": ["43.205.87.144"]
}
},
-61
View File
@@ -1,61 +0,0 @@
const core = require('@actions/core');
const getJSON = require('../utils/getJSON.js');
const checkInfo = require('../utils/checkInfo.js');
const checkRecords = require('../utils/checkRecords.js');
const getFileName = require('../utils/getFileName.js');
const data = getJSON(process.env.FILES);
function setupMessages() {
core.setOutput('infoMessage', "Could not validate info.");
core.setOutput('recordMessage', "Could not validate records.");
core.setOutput('jsonData', JSON.stringify(data, null, 2))
core.setOutput('shouldComment', 'true')
};
function outputInvalidJsonFile() {
core.setOutput('infoMessage', "Could not validate info.");
core.setOutput('recordMessage', "Could not validate records.");
core.setOutput('jsonData', "Could not read the JSON file, did you have an error in your syntax?")
core.setOutput('shouldComment', 'true')
}
beforeAll(() => {
setupMessages();
if (data == 3) {
outputInvalidJsonFile();
console.log("ERROR: JSON file is invalid. Either the file is not JSON, or there is an error in the JSON syntax.");
return process.exit(1)
};
if (data == false) {
core.setOutput('shouldComment', 'false')
console.log('INFO: File is not a subdomain JSON file.')
return process.exit(0)
};
});
test('check if json file has required info', async () => {
const passed = await checkInfo(data);
let infoMessage = passed === true ? "Valid information provided." : passed === "unknown" ? "Error verifying email address.\nA maintainer will have to manually verify your email address.\nReason:" : "Invalid information provided.\nPlease check your provided information.\nReason:";
core.setOutput('infoMessage', infoMessage);
expect(passed).toBeTruthy();
}, 15000);
test('Check if JSON file follows format', () => {
const passed = checkRecords(data);
let recordMessage = passed === true ? "Valid records provided." : "Invalid records provided.\nPlease check your provided records.\nThey should only be of type `A`, `CNAME` or `TXT`, and should follow their respective formats.";
core.setOutput('recordMessage', recordMessage);
expect(passed).toBe(true);
});
test('Check if JSON file matches the value name', () => {
const passed = getFileName(process.env.FILES, data);
let fileNameMessage = passed === true ? "File name matches target.RECORD_TYPE." : "File name does not match any target.RECORD_TYPE.";
core.setOutput('fileNameMessage', fileNameMessage);
expect(passed).toBe(true);
});
-27
View File
@@ -1,27 +0,0 @@
const fetch = require('node-fetch');
const core = require('@actions/core');
async function checkEmail(email) {
console.log(`Checking: ${email}`);
const url = `https://api.is-a-good.dev/email-check?key=6lPyUV2dX8&email=${encodeURIComponent(email)}`;
const options = {
method: 'GET'
}
const res = await fetch(url, options).then(res => res.json());
console.log(res);
core.setOutput('infoReason', res.reason);
if (res.data.valid) return true;
return false;
}
async function checkInfo(data) {
return await checkEmail(data.owner.email);
}
module.exports = checkInfo;
-35
View File
@@ -1,35 +0,0 @@
const { checkIfValidIP, checkIfValidIPv6, checkIfValidFQDN, checkInvalidDomain, isString } = require('./utils.js');
function checkRecords(data) {
const recordType = Object.keys(data.target)[0];
const value = data.target[recordType].value;
// Check if the domain is invalid
if (checkInvalidDomain(data.target[recordType].name) === true) return false;
// Check AAAA record
if (recordType.toLowerCase() === 'aaaa') {
if (!Array.isArray(value)) return false;
for (const record of value) {
if (checkIfValidIPv6(record) == false) return false;
}
return true;
}
// Check A record
if (recordType.toLowerCase() === 'a') {
if (!Array.isArray(value)) return false;
for (const record of value) {
if (checkIfValidIP(record) == false) return false;
}
return true;
}
// Check CNAME record
if (recordType.toLowerCase() === 'cname') {
if (!isString(value)) return false;
return checkIfValidFQDN(value);
}
return false;
}
module.exports = checkRecords;
-13
View File
@@ -1,13 +0,0 @@
const { stripExt } = require('./utils.js');
function getFileName(file, data) {
const fileNameWithoutExt = stripExt(file);
// Validate if the file name matches any of the target.RECORD_TYPE values
const recordTypes = Object.values(data.target);
return recordTypes[0].name == fileNameWithoutExt.toLowerCase().split("/")[1];
}
module.exports = getFileName;
-36
View File
@@ -1,36 +0,0 @@
const fs = require('fs');
const { getFileExtension, stripExt } = require('./utils.js');
const core = require('@actions/core');
function getJSON(file) {
const path = `${process.env.actions_path}/${file}`; // File path.
const ext = getFileExtension(file);
if (path.includes("sub-logs") && !ext || ext != 'json') return 3 //if file is subdomain file but has no ext or diff ext than .json
if (!ext) return false; // If no file extension, return.
if (ext != 'json') return false; // If file extension is not '.json' return.
try {
if (fs.existsSync(path)) { // Check if file exists in domain directory
// It exists
const rawdata = fs.readFileSync(path); // Read the file
const data = JSON.parse(rawdata); // Parse it
return data; // Return the parsed data if validation passes
}
return 3; // It doesn't exist
} catch(err) {
core.setOutput('infoMessage', "Could not validate info.");
core.setOutput('recordMessage', "Could not validate records.");
core.setOutput('jsonData', "Could not read the JSON file, did you have an error in your syntax?")
core.setOutput('shouldComment', 'true')
console.log(err);
return 3;
}
return 3;
}
module.exports = getJSON;
-52
View File
@@ -1,52 +0,0 @@
[
"*",
"_acme-challenge",
"_github-challenge-is-a-good-dev",
"_github-pages-challenge-is-a-good-dev",
"email",
"example",
"help",
"mail",
"no-reply",
"noreply",
"notification",
"notifications",
"support",
"will",
"ww",
"ww1",
"ww2",
"ww3",
"ww4",
"ww5",
"ww6",
"ww7",
"ww8",
"ww9",
"wwww",
"your-domain-name",
"www",
"docs",
"cdn",
"dashboard",
"create",
"documentation",
"doc",
"content",
"network",
"shop",
"test",
"php",
"js",
"html",
"css",
"wordpress",
"wp",
"info",
"information",
"admin",
"administration",
"json",
"dev",
"developer"
]
-74
View File
@@ -1,74 +0,0 @@
const invalidDomains = require('./invalid-domains.json');
const fs = require('fs');
const core = require('@actions/core');
function getFiles(path) {
// Get all files in path: returns array
const allFiles = fs.readdirSync(path);
return allFiles;
}
function getFileExtension(filename) {
// Get file extension
return (/[.]/.exec(filename)) ? /[^.]+$/.exec(filename) : undefined;
}
function stripExt(filename) {
// Remove file extension
return filename.substr(0, filename.lastIndexOf('.'));
}
function checkIfValidIP(str) {
// Check if the IP address is valid
const regexExp = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/gm;
return regexExp.test(str);
}
function checkIfValidIPv6(str) {
// Insane regex to check if IPv6 address is valid.
const regexExp = /^(?:(?:[a-fA-F\d]{1,4}:){7}(?:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){6}(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|:[a-fA-F\d]{1,4}|:)|(?:[a-fA-F\d]{1,4}:){5}(?::(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,2}|:)|(?:[a-fA-F\d]{1,4}:){4}(?:(?::[a-fA-F\d]{1,4}){0,1}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,3}|:)|(?:[a-fA-F\d]{1,4}:){3}(?:(?::[a-fA-F\d]{1,4}){0,2}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,4}|:)|(?:[a-fA-F\d]{1,4}:){2}(?:(?::[a-fA-F\d]{1,4}){0,3}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,5}|:)|(?:[a-fA-F\d]{1,4}:){1}(?:(?::[a-fA-F\d]{1,4}){0,4}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,6}|:)|(?::(?:(?::[a-fA-F\d]{1,4}){0,5}:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:\\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3}|(?::[a-fA-F\d]{1,4}){1,7}|:)))(?:%[0-9a-zA-Z]{1,})?$/gm;
return regexExp.test(str);
}
function checkIfValidFQDN(str) {
// Check if the FQDN is valid
const regexExp = /(?=^.{4,253}$)(^((?!-)[a-zA-Z0-9-]{0,62}[a-zA-Z0-9]\.)+[a-zA-Z]{2,63}$)/gi;
return regexExp.test(str);
}
function checkInvalidDomain(str) {
// Check invalid domains
if (invalidDomains.includes(str)) {
core.setOutput("recordInfo", "This subdomain has been blocked.")
return true;
}
// Check reserved domains
const files = getFiles("./sub-logs/reserved/").map(file => stripExt(file));
if (files.includes(str)) {
core.setOutput("recordInfo", "This subdomain has been reserved by the is-a-good.dev team.")
return true;
}
return false;
}
function isString(value) {
return typeof value === 'string' || value instanceof String;
}
module.exports = {
checkInvalidDomain,
checkIfValidFQDN,
checkIfValidIP,
checkIfValidIPv6,
stripExt,
getFileExtension,
getFiles,
isString
}