diff --git a/cli/main.go b/cli/main.go index 077ed51e..3b9fee1c 100644 --- a/cli/main.go +++ b/cli/main.go @@ -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)) } diff --git a/cli/update_viktorbarzin_me_technitium.go b/cli/update_viktorbarzin_me_technitium.go new file mode 100644 index 00000000..4b951dba --- /dev/null +++ b/cli/update_viktorbarzin_me_technitium.go @@ -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 +} diff --git a/main.tf b/main.tf index ed029eb6..0b43ba4a 100644 --- a/main.tf +++ b/main.tf @@ -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 } diff --git a/modules/kubernetes/infra-maintenance/main.tf b/modules/kubernetes/infra-maintenance/main.tf index e70bfddf..a1b7815e 100644 --- a/modules/kubernetes/infra-maintenance/main.tf +++ b/modules/kubernetes/infra-maintenance/main.tf @@ -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" diff --git a/modules/kubernetes/main.tf b/modules/kubernetes/main.tf index 8dbb488b..7ae8da3d 100644 --- a/modules/kubernetes/main.tf +++ b/modules/kubernetes/main.tf @@ -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" { diff --git a/terraform.tfstate b/terraform.tfstate index 3fee76bd..8c7de484 100644 Binary files a/terraform.tfstate and b/terraform.tfstate differ diff --git a/terraform.tfvars b/terraform.tfvars index 46ff57df..6b3fd83d 100644 Binary files a/terraform.tfvars and b/terraform.tfvars differ