update dns update ip job to update technitium via api

This commit is contained in:
Viktor Barzin 2024-01-23 20:30:39 +00:00
parent 8f7ece5dac
commit 543fca5dbb
5 changed files with 222 additions and 33 deletions

View file

@ -195,30 +195,32 @@ func run() error {
return nil
}
// Send notification as glue records can't be modified programatically for godaddy :/
err = notifyForIPChange(publicDNSIp, dynamicDNSIp)
if err != nil {
return errors.Wrapf(err, "failed to notify for ip change. this must succeed otherwise the glue records won't be updated")
}
defer notifyForIPChange(publicDNSIp, dynamicDNSIp)
// setup git repo
gitFs, err := NewGitFS(repository)
if err != nil {
return errors.Wrapf(err, "failed to initialize git fs")
}
worktree, err := gitFs.repo.Worktree()
if err != nil {
return errors.Wrapf(err, "failed to get worktree")
}
err = updatePublicIP(gitFs, publicDNSIp, dynamicDNSIp)
if err != nil {
return fmt.Errorf("failed to update public ip: %w", err)
}
// // commit changes
if _, err = worktree.Commit("Update public ip and ns records", &git.CommitOptions{All: true, Author: &object.Signature{Name: "Webhook Handler Bot"}}); err != nil {
return errors.Wrapf(err, "failed to commit")
}
if err = gitFs.Push(); err != nil {
return errors.Wrapf(err, "failed to push changes")
}
// Old, code-as-infra based approach
// gitFs, err := NewGitFS(repository)
// if err != nil {
// return errors.Wrapf(err, "failed to initialize git fs")
// }
// worktree, err := gitFs.repo.Worktree()
// if err != nil {
// return errors.Wrapf(err, "failed to get worktree")
// }
// err = updatePublicIP(gitFs, publicDNSIp, dynamicDNSIp)
// if err != nil {
// return fmt.Errorf("failed to update public ip: %w", err)
// }
// // // commit changes
// if _, err = worktree.Commit("Update public ip and ns records", &git.CommitOptions{All: true, Author: &object.Signature{Name: "Webhook Handler Bot"}}); err != nil {
// return errors.Wrapf(err, "failed to commit")
// }
// if err = gitFs.Push(); err != nil {
// return errors.Wrapf(err, "failed to push changes")
// }
username := os.Getenv("TECHNITIUM_USERNAME")
password := os.Getenv("TECHNITIUM_PASSWORD")
// dynamicDNSIp = net.ParseIP("6.9.6.9")
return UpdatePublicIPViaTechnitiumAPI(dynamicDNSIp, username, password)
default:
err = errors.New(fmt.Sprintf("unsupported use case: %s", *useCase))
}

View file

@ -0,0 +1,167 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"net/url"
"strings"
"github.com/pkg/errors"
)
type CreateTokenResponse struct {
Username string `json:"username"`
TokenName string `json:"tokenName"`
Token string `json:"token"`
Status string `json:"status"`
ErrorMessage string `json:"errorMessage"`
}
type GetRecordsResponse struct {
Response struct {
Zone struct {
Name string `json:"name"`
Type string `json:"type"`
Internal bool `json:"internal"`
DnssecStatus string `json:"dnssecStatus"`
Disabled bool `json:"disabled"`
} `json:"zone"`
Records []struct {
Disabled bool `json:"disabled"`
Name string `json:"name"`
Type string `json:"type"`
Ttl int64 `json:"ttl"`
RData struct {
IpAddress string `json:"ipAddress"`
// there's more fields that we don't use atm
} `json:"rData"`
// RData interface{} `json:"rData"`
DnsSecStatus string `json:"dnsSecStatus"`
} `json:"records"`
} `json:"response"`
}
type UpdateRecordResponse struct {
Status string `json:"status"`
ErrorMessage string `json:"errorMessage"`
}
// const TECHNITIUM_HOST = "technitium-web.technitium"
const TECHNITIUM_HOST = "localhost"
func UpdatePublicIPViaTechnitiumAPI(newIp net.IP, username string, password string) error {
token, err := createTechnitiumToken(username, password)
if err != nil {
return errors.Wrap(err, "failed to get technitium token")
}
for _, ns := range []string{"ns1", "ns2"} {
nsRecordName := ns + ".viktorbarzin.me"
currIpStr, err := getRecordValue(token, nsRecordName, "A")
if err != nil {
return errors.Wrap(err, "failed to get A record for ns server")
}
currIp := net.ParseIP(currIpStr)
fmt.Printf("updating record %s to %s\n", nsRecordName, newIp.String())
err = UpdateTechnitiumNSARecord(token, nsRecordName, currIp, newIp)
if err != nil {
return errors.Wrap(err, "failed to update NS A record")
}
}
return nil
}
func UpdateTechnitiumNSARecord(token, domain string, currIp, newIp net.IP) error {
baseURL := fmt.Sprintf("http://%s:5380/api/zones/records/update", TECHNITIUM_HOST)
params := map[string]string{
"token": token,
"domain": domain,
"type": "A",
"newIpAddress": newIp.String(),
"ipAddress": currIp.String(),
}
resp, err := sendTechnitiumAPIRequest(baseURL, params)
if err != nil {
return errors.Wrap(err, "failed to update record")
}
var parsedResponse UpdateRecordResponse
err = json.NewDecoder(strings.NewReader(resp)).Decode(&parsedResponse)
if err != nil {
return errors.Wrap(err, "failed to decode json response when updating record")
}
if parsedResponse.Status == "error" {
return fmt.Errorf("received error status when updating record: %s", parsedResponse.ErrorMessage)
}
return nil
}
func createTechnitiumToken(username string, password string) (string, error) {
baseURL := fmt.Sprintf("http://%s:5380/api/user/createToken", TECHNITIUM_HOST)
params := map[string]string{
"user": username,
"pass": password,
"tokenName": "infra-cli-token",
}
resp, err := sendTechnitiumAPIRequest(baseURL, params)
if err != nil {
return "", errors.Wrap(err, "failed to fetch token")
}
var tokenResponse CreateTokenResponse
// println(resp)
err = json.NewDecoder(strings.NewReader(resp)).Decode(&tokenResponse)
if err != nil {
return "", errors.Wrap(err, "failed to decode json response")
}
if tokenResponse.Status != "ok" {
return "", fmt.Errorf("received error status when fetching token: %s, error: %s", tokenResponse.Status, tokenResponse.ErrorMessage)
}
return tokenResponse.Token, nil
}
func getRecordValue(token, domain, recordType string) (string, error) {
baseURL := fmt.Sprintf("http://%s:5380/api/zones/records/get", TECHNITIUM_HOST)
params := map[string]string{
"token": token,
"domain": domain,
}
resp, err := sendTechnitiumAPIRequest(baseURL, params)
if err != nil {
return "", errors.Wrapf(err, "failed to fetch record values for domain %s", domain)
}
var response GetRecordsResponse
err = json.NewDecoder(strings.NewReader(resp)).Decode(&response)
if err != nil {
return "", errors.Wrap(err, "failed to decode json response when getting all zone records")
}
for _, record := range response.Response.Records {
if record.Type == recordType {
return record.RData.IpAddress, nil
}
}
return "", fmt.Errorf("failed to find record for name %s and type %s", domain, recordType)
}
func sendTechnitiumAPIRequest(baseURL string, params map[string]string) (string, error) {
url, err := url.Parse(baseURL)
if err != nil {
return "", errors.Wrapf(err, "failed to create base url")
}
// Encode the URL parameters
query := url.Query()
for key, value := range params {
query.Add(key, value)
}
url.RawQuery = query.Encode()
resp, err := http.Get(url.String())
if err != nil {
return "", errors.Wrap(err, "failed to create token")
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
return string(body), err
}

View file

@ -44,6 +44,8 @@ variable "webhook_handler_fb_verify_token" {}
variable "webhook_handler_fb_page_token" {}
variable "webhook_handler_fb_app_secret" {}
variable "webhook_handler_git_user" {}
variable "technitium_username" {}
variable "technitium_password" {}
variable "webhook_handler_git_token" {}
variable "webhook_handler_ssh_key" {}
variable "monitoring_idrac_username" {}
@ -344,6 +346,10 @@ module "kubernetes_cluster" {
resume_database_url = var.resume_database_url
frigate_valchedrym_camera_credentials = var.frigate_valchedrym_camera_credentials
// updating technitium records
technitium_username = var.technitium_username
technitium_password = var.technitium_password
}

View file

@ -1,6 +1,8 @@
# Module to run some infra-specific things like updating the public ip
variable git_user {}
variable git_token {}
variable "git_user" {}
variable "git_token" {}
variable "technitium_username" {}
variable "technitium_password" {}
resource "kubernetes_cron_job_v1" "update-public-ip" {
@ -23,21 +25,29 @@ resource "kubernetes_cron_job_v1" "update-public-ip" {
spec {
priority_class_name = "system-cluster-critical"
container {
name = "update-public-ip"
image = "viktorbarzin/infra"
name = "update-public-ip"
image = "viktorbarzin/infra"
command = ["./infra_cli"]
args = ["-use-case", "update-public-ip"]
env {
name = "GIT_USER"
name = "GIT_USER"
value = var.git_user
}
env {
name = "GIT_TOKEN"
name = "GIT_TOKEN"
value = var.git_token
}
env {
name = "TECHNITIUM_USERNAME"
value = var.technitium_username
}
env {
name = "TECHNITIUM_PASSWORD"
value = var.technitium_password
}
}
restart_policy = "Never"
restart_policy = "Never"
# service_account_name = "descheduler-sa"
# volume {
# name = "policy-volume"

View file

@ -33,6 +33,8 @@ variable "webhook_handler_fb_app_secret" {}
variable "webhook_handler_git_user" {}
variable "webhook_handler_git_token" {}
variable "webhook_handler_ssh_key" {}
variable "technitium_username" {}
variable "technitium_password" {}
variable "idrac_username" {}
variable "idrac_password" {}
variable "alertmanager_slack_api_url" {}
@ -285,9 +287,11 @@ module "excalidraw" {
}
module "infra-maintenance" {
source = "./infra-maintenance"
git_user = var.webhook_handler_git_user
git_token = var.webhook_handler_git_token
source = "./infra-maintenance"
git_user = var.webhook_handler_git_user
git_token = var.webhook_handler_git_token
technitium_username = var.technitium_username
technitium_password = var.technitium_password
}
module "travel_blog" {