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 return nil
} }
// Send notification as glue records can't be modified programatically for godaddy :/ // Send notification as glue records can't be modified programatically for godaddy :/
err = notifyForIPChange(publicDNSIp, dynamicDNSIp) defer 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")
}
// setup git repo // setup git repo
gitFs, err := NewGitFS(repository) // Old, code-as-infra based approach
if err != nil { // gitFs, err := NewGitFS(repository)
return errors.Wrapf(err, "failed to initialize git fs") // if err != nil {
} // return errors.Wrapf(err, "failed to initialize git fs")
worktree, err := gitFs.repo.Worktree() // }
if err != nil { // worktree, err := gitFs.repo.Worktree()
return errors.Wrapf(err, "failed to get worktree") // if err != nil {
} // return errors.Wrapf(err, "failed to get worktree")
err = updatePublicIP(gitFs, publicDNSIp, dynamicDNSIp) // }
if err != nil { // err = updatePublicIP(gitFs, publicDNSIp, dynamicDNSIp)
return fmt.Errorf("failed to update public ip: %w", err) // 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 { // // // commit changes
return errors.Wrapf(err, "failed to commit") // 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") // 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: default:
err = errors.New(fmt.Sprintf("unsupported use case: %s", *useCase)) 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_page_token" {}
variable "webhook_handler_fb_app_secret" {} variable "webhook_handler_fb_app_secret" {}
variable "webhook_handler_git_user" {} variable "webhook_handler_git_user" {}
variable "technitium_username" {}
variable "technitium_password" {}
variable "webhook_handler_git_token" {} variable "webhook_handler_git_token" {}
variable "webhook_handler_ssh_key" {} variable "webhook_handler_ssh_key" {}
variable "monitoring_idrac_username" {} variable "monitoring_idrac_username" {}
@ -344,6 +346,10 @@ module "kubernetes_cluster" {
resume_database_url = var.resume_database_url resume_database_url = var.resume_database_url
frigate_valchedrym_camera_credentials = var.frigate_valchedrym_camera_credentials 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 # Module to run some infra-specific things like updating the public ip
variable git_user {} variable "git_user" {}
variable git_token {} variable "git_token" {}
variable "technitium_username" {}
variable "technitium_password" {}
resource "kubernetes_cron_job_v1" "update-public-ip" { resource "kubernetes_cron_job_v1" "update-public-ip" {
@ -23,21 +25,29 @@ resource "kubernetes_cron_job_v1" "update-public-ip" {
spec { spec {
priority_class_name = "system-cluster-critical" priority_class_name = "system-cluster-critical"
container { container {
name = "update-public-ip" name = "update-public-ip"
image = "viktorbarzin/infra" image = "viktorbarzin/infra"
command = ["./infra_cli"] command = ["./infra_cli"]
args = ["-use-case", "update-public-ip"] args = ["-use-case", "update-public-ip"]
env { env {
name = "GIT_USER" name = "GIT_USER"
value = var.git_user value = var.git_user
} }
env { env {
name = "GIT_TOKEN" name = "GIT_TOKEN"
value = var.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" # service_account_name = "descheduler-sa"
# volume { # volume {
# name = "policy-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_user" {}
variable "webhook_handler_git_token" {} variable "webhook_handler_git_token" {}
variable "webhook_handler_ssh_key" {} variable "webhook_handler_ssh_key" {}
variable "technitium_username" {}
variable "technitium_password" {}
variable "idrac_username" {} variable "idrac_username" {}
variable "idrac_password" {} variable "idrac_password" {}
variable "alertmanager_slack_api_url" {} variable "alertmanager_slack_api_url" {}
@ -285,9 +287,11 @@ module "excalidraw" {
} }
module "infra-maintenance" { module "infra-maintenance" {
source = "./infra-maintenance" source = "./infra-maintenance"
git_user = var.webhook_handler_git_user git_user = var.webhook_handler_git_user
git_token = var.webhook_handler_git_token git_token = var.webhook_handler_git_token
technitium_username = var.technitium_username
technitium_password = var.technitium_password
} }
module "travel_blog" { module "travel_blog" {