diff --git a/devops/nodedb-rest-api/.gitignore b/devops/nodedb-rest-api/.gitignore new file mode 100644 index 0000000..0dc7ff0 --- /dev/null +++ b/devops/nodedb-rest-api/.gitignore @@ -0,0 +1,2 @@ +.idea/ +bin/* diff --git a/devops/nodedb-rest-api/Makefile b/devops/nodedb-rest-api/Makefile new file mode 100644 index 0000000..39bcc1c --- /dev/null +++ b/devops/nodedb-rest-api/Makefile @@ -0,0 +1,5 @@ +build: + go mod tidy + go build -o bin/go-aws-ec2 cmd/aws/main.go + go build -o bin/go-hetzner-vps cmd/hetzner/main.go + go build -o bin/go-digital-ocean-droplet cmd/do/main.go diff --git a/devops/nodedb-rest-api/README.md b/devops/nodedb-rest-api/README.md new file mode 100644 index 0000000..c2c14d7 --- /dev/null +++ b/devops/nodedb-rest-api/README.md @@ -0,0 +1,54 @@ +### AWS Cloud + +#### SystemD Configuration (/etc/systemd/system/go-aws-ec2.service) +```text +[Unit] +Description=go-aws-ec2 +After=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=ubuntu +Group=ubuntu +WorkingDirectory=/opt/apps/go-aws-ec2 +ExecStart=/opt/apps/go-aws-ec2/go-aws-ec2 --client-ip client-ip.json +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target +``` + +### Digital Ocean Cloud + +#### OAuth Configuration (configuration.json) + +```text +1. Login Digital Ocean and Create OAuth Client Application +2. configure "configuration.json" + 2.1 homeUri + 2.2 redirectUri + 2.3 clientId + 2.4 clientSecret +``` + +#### SystemD Configuration (/etc/systemd/system/go-digital-ocean-droplet.service) +```text +[Unit] +Description=go-digital-ocean-droplet +After=network-online.target + +[Service] +Type=simple +Restart=always +RestartSec=1 +User=root +Group=root +WorkingDirectory=/opt/apps/go-digital-ocean-droplet +ExecStart=/opt/apps/go-digital-ocean-droplet/go-digital-ocean-droplet --client-ip client-ip.json --configuration configuration.json +StartLimitInterval=0 + +[Install] +WantedBy=multi-user.target +``` diff --git a/devops/nodedb-rest-api/client-ip.json b/devops/nodedb-rest-api/client-ip.json new file mode 100644 index 0000000..a80bd92 --- /dev/null +++ b/devops/nodedb-rest-api/client-ip.json @@ -0,0 +1,3 @@ +[ + "127.0.0.1" +] diff --git a/devops/nodedb-rest-api/cmd/aws/main.go b/devops/nodedb-rest-api/cmd/aws/main.go new file mode 100644 index 0000000..c7c1088 --- /dev/null +++ b/devops/nodedb-rest-api/cmd/aws/main.go @@ -0,0 +1,201 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "log" + "net" + "net/http" + "nodedb-rest-api/pkg/nodedb" + "os" + "os/signal" + "time" + + "github.com/gorilla/mux" +) + +var serverCertificate string +var serverPrivateKey string +var serverMtls string + +var gracefulTimeout time.Duration +var clientIpFile string +var serverPort int + +func main() { + flag.IntVar(&serverPort, "server-port", 8080, "server port") + flag.StringVar(&serverCertificate, "server-certificate", "", "") + flag.StringVar(&serverPrivateKey, "server-private-key", "", "") + flag.StringVar(&serverMtls, "server-mtls", "", "") + + flag.DurationVar(&gracefulTimeout, "graceful-timeout", time.Second*15, "the duration for which the server gracefully gracefulTimeout for existing connections to finish - e.g. 15s or 1m") + + flag.StringVar(&clientIpFile, "client-ip", "client-ip.json", "client ip which allow to access") + + flag.Parse() + + r := mux.NewRouter() + + // Add your routes as needed + r.HandleFunc("/", Home) + + http.Handle("/", r) + + var hasTLS = false + if serverCertificate != "" && serverPrivateKey != "" { + hasTLS = true + } + + var srv = nodedb.ConfigurationServer(serverPort, serverCertificate, serverPrivateKey, serverMtls) + srv.Handler = r + + // Run our server in a goroutine so that it doesn't block. + go func() { + nodedb.InfoIP(hasTLS, serverPort) + if hasTLS { + if errorListenAndServe := srv.ListenAndServeTLS(serverCertificate, serverPrivateKey); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } else { + if errorListenAndServe := srv.ListenAndServe(); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to gracefulTimeout for. + ctx, cancel := context.WithTimeout(context.Background(), gracefulTimeout) + defer cancel() + // Doesn't block if no connections, but will otherwise gracefulTimeout + // until the timeout deadline. + errorShutdown := srv.Shutdown(ctx) + + if errorShutdown != nil { + println(errorShutdown.Error()) + } + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should gracefulTimeout for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} + +func Home(w http.ResponseWriter, r *http.Request) { + _context := context.Background() + + clientIp, _, errorSplitHostPort := net.SplitHostPort(r.RemoteAddr) + if errorSplitHostPort != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + allow, errorAllowClient := nodedb.AllowClient(clientIpFile, clientIp) + + if errorAllowClient != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorAllowClient.Error()) + return + } + + if !*allow { + w.WriteHeader(http.StatusForbidden) + _, _ = fmt.Fprintf(w, "Your IP Address %s is not allow", clientIp) + return + } + + _config, errorLoadDefaultConfig := config.LoadDefaultConfig(_context, func(options *config.LoadOptions) error { + options.Region = "us-west-2" + return nil + }) + + if errorLoadDefaultConfig != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorLoadDefaultConfig.Error()) + return + } + + client := ec2.NewFromConfig(_config) + + _regions, errorDescribeRegions := client.DescribeRegions(_context, &ec2.DescribeRegionsInput{}) + + if errorDescribeRegions != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorDescribeRegions.Error()) + return + } + + var _ec2s []nodedb.Ec2Dto + + for _, region := range _regions.Regions { + + _describeInstancesInput := &ec2.DescribeInstancesInput{} + + NextRequest: + + _describeInstancesOutput, errorDescribeInstances := client.DescribeInstances(_context, _describeInstancesInput, func(options *ec2.Options) { + options.Region = *region.RegionName + }) + + if errorDescribeInstances == nil { + for _, reservation := range _describeInstancesOutput.Reservations { + for _, instance := range reservation.Instances { + _instanceId := *instance.InstanceId + _publicIpAddress := "" + _privateIpAddress := "" + _tagName := "" + if instance.PublicIpAddress != nil { + _publicIpAddress = *instance.PublicIpAddress + } + if instance.PrivateIpAddress != nil { + _privateIpAddress = *instance.PrivateIpAddress + } + for _, tag := range instance.Tags { + if tag.Key != nil && *tag.Key == "Name" && tag.Value != nil { + _tagName = *tag.Value + } + } + _ec2s = append(_ec2s, nodedb.Ec2Dto{ + TagName: _tagName, + InstanceId: _instanceId, + PublicIP: _publicIpAddress, + PrivateIP: _privateIpAddress, + Region: *region.RegionName, + }) + } + } + } else { + println(errorDescribeInstances.Error()) + } + + if _describeInstancesOutput.NextToken != nil { + _describeInstancesInput = &ec2.DescribeInstancesInput{} + _describeInstancesInput.NextToken = _describeInstancesOutput.NextToken + goto NextRequest + } + } + + _json, errorMarshalIndent := json.MarshalIndent(_ec2s, "", " ") + + if errorMarshalIndent != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorMarshalIndent.Error()) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(_json) +} diff --git a/devops/nodedb-rest-api/cmd/do/main.go b/devops/nodedb-rest-api/cmd/do/main.go new file mode 100644 index 0000000..2aab454 --- /dev/null +++ b/devops/nodedb-rest-api/cmd/do/main.go @@ -0,0 +1,348 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "github.com/digitalocean/godo" + "io" + "log" + "net" + "net/http" + "net/url" + "nodedb-rest-api/pkg/nodedb" + "os" + "os/signal" + "strings" + "time" + + "github.com/gorilla/mux" +) + +var gracefulTimeout time.Duration + +var serverCertificate string +var serverPrivateKey string +var serverMtls string + +var clientIpFile string +var configurationFile string +var serverPort int + +var accessToken string +var refreshToken string + +func main() { + flag.IntVar(&serverPort, "server-port", 8080, "server port") + flag.StringVar(&serverCertificate, "server-certificate", "", "") + flag.StringVar(&serverPrivateKey, "server-private-key", "", "") + flag.StringVar(&serverMtls, "server-mtls", "", "") + + flag.DurationVar(&gracefulTimeout, "graceful-timeout", time.Second*15, "the duration for which the server gracefully gracefulTimeout for existing connections to finish - e.g. 15s or 1m") + + flag.StringVar(&clientIpFile, "client-ip", "client-ip.json", "client ip which allow to access") + flag.StringVar(&configurationFile, "configuration", "configuration.json", "configuration") + + flag.Parse() + + r := mux.NewRouter() + + // Add your routes as needed + r.HandleFunc("/", Home) + r.HandleFunc("/callback", Callback) + + http.Handle("/", r) + + go RefreshToken() + + var hasTLS = false + if serverCertificate != "" && serverPrivateKey != "" { + hasTLS = true + } + + var srv = nodedb.ConfigurationServer(serverPort, serverCertificate, serverPrivateKey, serverMtls) + srv.Handler = r + + // Run our server in a goroutine so that it doesn't block. + go func() { + nodedb.InfoIP(hasTLS, serverPort) + if hasTLS { + if errorListenAndServe := srv.ListenAndServeTLS(serverCertificate, serverPrivateKey); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } else { + if errorListenAndServe := srv.ListenAndServe(); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to gracefulTimeout for. + ctx, cancel := context.WithTimeout(context.Background(), gracefulTimeout) + defer cancel() + // Doesn't block if no connections, but will otherwise gracefulTimeout + // until the timeout deadline. + errorShutdown := srv.Shutdown(ctx) + + if errorShutdown != nil { + println(errorShutdown.Error()) + } + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should gracefulTimeout for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} + +func Home(w http.ResponseWriter, r *http.Request) { + clientIp, _, errorSplitHostPort := net.SplitHostPort(r.RemoteAddr) + if errorSplitHostPort != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + allow, errorAllowClient := nodedb.AllowClient(clientIpFile, clientIp) + + if errorAllowClient != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorAllowClient.Error()) + return + } + + if !*allow { + w.WriteHeader(http.StatusForbidden) + _, _ = fmt.Fprintf(w, "Your IP Address %s is not allow", clientIp) + return + } + + if accessToken == "" { + + configuration, errorLookupConfiguration := LookupConfiguration() + if errorLookupConfiguration != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorLookupConfiguration.Error()) + return + } + + http.Redirect(w, r, fmt.Sprintf("%s/authorize?client_id=%s&redirect_uri=%s&response_type=code", configuration.DigitalOcean.OAuthUrl, configuration.DigitalOcean.ClientId, configuration.RedirectUri), http.StatusSeeOther) + return + } + + client := godo.NewFromToken(accessToken) + + _context := context.Background() + + var _droplets []nodedb.DigitalOceanDto + + var _listDroplets []godo.Droplet + + _listOptions := &godo.ListOptions{} + for { + droplets, resp, errorList := client.Droplets.List(_context, _listOptions) + if errorList != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + // append the current page's droplets to our list + _listDroplets = append(_listDroplets, droplets...) + + // if we are at the last page, break out the for loop + if resp.Links == nil || resp.Links.IsLastPage() { + break + } + + page, errorCurrentPage := resp.Links.CurrentPage() + if errorCurrentPage != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorCurrentPage.Error()) + return + } + + // set the page we want for the next request + _listOptions.Page = page + 1 + } + + for _, droplet := range _listDroplets { + _publicIPv4Address, _ := droplet.PublicIPv4() + _publicIPv6Address, _ := droplet.PublicIPv6() + _privateIPv4Address, _ := droplet.PrivateIPv4() + _droplets = append(_droplets, nodedb.DigitalOceanDto{ + Id: droplet.ID, + Region: droplet.Region.Name, + Name: droplet.Name, + PrivateIPv4: _privateIPv4Address, + PublicIPv4: _publicIPv4Address, + PublicIPv6: _publicIPv6Address, + Tags: droplet.Tags, + }) + } + + _json, errorMarshalIndent := json.MarshalIndent(_droplets, "", " ") + + if errorMarshalIndent != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorMarshalIndent.Error()) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(_json) +} + +func Callback(w http.ResponseWriter, r *http.Request) { + clientIp, _, errorSplitHostPort := net.SplitHostPort(r.RemoteAddr) + if errorSplitHostPort != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + allow, errorAllowClient := nodedb.AllowClient(clientIpFile, clientIp) + + if errorAllowClient != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorAllowClient.Error()) + return + } + + if !*allow { + w.WriteHeader(http.StatusForbidden) + _, _ = fmt.Fprintf(w, "Your IP Address %s is not allow", clientIp) + return + } + + configuration, errorLookupConfiguration := LookupConfiguration() + if errorLookupConfiguration != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorLookupConfiguration.Error()) + return + } + + code := r.URL.Query().Get("code") + + data := url.Values{} + data.Set("grant_type", "authorization_code") + data.Set("code", code) + data.Set("client_id", configuration.DigitalOcean.ClientId) + data.Set("client_secret", configuration.DigitalOcean.ClientSecret) + data.Set("redirect_uri", configuration.RedirectUri) + encode := data.Encode() + body := strings.NewReader(encode) + + client := &http.Client{} + request, errorNewRequest := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/token", configuration.DigitalOcean.OAuthUrl), body) + if errorNewRequest != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorNewRequest.Error()) + return + } + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, errorDo := client.Do(request) + + if errorDo != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorDo.Error()) + return + } + + bodyContent, errorReadAll := io.ReadAll(resp.Body) + + if errorReadAll != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorReadAll.Error()) + return + } + + var oauthToken nodedb.DigitalOceanOAuthToken + + errorUnmarshal1 := json.Unmarshal(bodyContent, &oauthToken) + + if errorUnmarshal1 != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorUnmarshal1.Error()) + return + } + + accessToken = oauthToken.AccessToken + refreshToken = oauthToken.RefreshToken + + http.Redirect(w, r, configuration.HomeUri, http.StatusSeeOther) +} + +func RefreshToken() { + for { + if refreshToken != "" { + + configuration, errorLookupConfiguration := LookupConfiguration() + if errorLookupConfiguration != nil { + log.Println(errorLookupConfiguration.Error()) + } else { + + data := url.Values{} + data.Set("grant_type", "refresh_token") + data.Set("refresh_token", refreshToken) + encode := data.Encode() + body := strings.NewReader(encode) + + client := &http.Client{} + request, errorNewRequest := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/token", configuration.DigitalOcean.OAuthUrl), body) + if errorNewRequest != nil { + log.Println(errorNewRequest.Error()) + } else { + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, errorDo := client.Do(request) + + if errorDo != nil { + log.Println(errorDo.Error()) + } else { + bodyContent, errorReadAll := io.ReadAll(resp.Body) + if errorReadAll != nil { + log.Println(errorReadAll.Error()) + } else { + var oauthToken nodedb.DigitalOceanOAuthToken + errorUnmarshal1 := json.Unmarshal(bodyContent, &oauthToken) + if errorUnmarshal1 != nil { + log.Println(errorUnmarshal1.Error()) + } else { + accessToken = oauthToken.AccessToken + refreshToken = oauthToken.RefreshToken + } + } + } + } + } + } + time.Sleep(5 * 60 * 1000 * time.Millisecond) + } +} + +func LookupConfiguration() (*nodedb.ConfigurationDto, error) { + configurationContent, errorReadFile := os.ReadFile(configurationFile) + if errorReadFile != nil { + return nil, errorReadFile + } + + var dto nodedb.ConfigurationDto + errorUnmarshal := json.Unmarshal(configurationContent, &dto) + + if errorUnmarshal != nil { + return nil, errorUnmarshal + } + + return &dto, nil +} diff --git a/devops/nodedb-rest-api/cmd/hetzner/main.go b/devops/nodedb-rest-api/cmd/hetzner/main.go new file mode 100644 index 0000000..5293189 --- /dev/null +++ b/devops/nodedb-rest-api/cmd/hetzner/main.go @@ -0,0 +1,183 @@ +package main + +import ( + "context" + "encoding/json" + "flag" + "fmt" + "github.com/hetznercloud/hcloud-go/hcloud" + "log" + "net" + "net/http" + "nodedb-rest-api/pkg/nodedb" + "os" + "os/signal" + "time" + + "github.com/gorilla/mux" +) + +var gracefulTimeout time.Duration +var clientIpFile string +var serverPort int + +var serverCertificate string +var serverPrivateKey string +var serverMtls string + +var accessToken string + +func main() { + flag.IntVar(&serverPort, "server-port", 8080, "server port") + flag.StringVar(&serverCertificate, "server-certificate", "", "") + flag.StringVar(&serverPrivateKey, "server-private-key", "", "") + flag.StringVar(&serverMtls, "server-mtls", "", "") + + flag.DurationVar(&gracefulTimeout, "graceful-timeout", time.Second*15, "the duration for which the server gracefully gracefulTimeout for existing connections to finish - e.g. 15s or 1m") + flag.StringVar(&clientIpFile, "client-ip", "client-ip.json", "client ip which allow to access") + + flag.StringVar(&accessToken, "access-token", "", "") + + flag.Parse() + + r := mux.NewRouter() + + // Add your routes as needed + r.HandleFunc("/", Home) + + http.Handle("/", r) + + var hasTLS = false + if serverCertificate != "" && serverPrivateKey != "" { + hasTLS = true + } + + var srv = nodedb.ConfigurationServer(serverPort, serverCertificate, serverPrivateKey, serverMtls) + srv.Handler = r + + // Run our server in a goroutine so that it doesn't block. + go func() { + nodedb.InfoIP(hasTLS, serverPort) + if hasTLS { + if errorListenAndServe := srv.ListenAndServeTLS(serverCertificate, serverPrivateKey); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } else { + if errorListenAndServe := srv.ListenAndServe(); errorListenAndServe != nil { + log.Println(errorListenAndServe.Error()) + } + } + }() + + c := make(chan os.Signal, 1) + // We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) + // SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught. + signal.Notify(c, os.Interrupt) + + // Block until we receive our signal. + <-c + + // Create a deadline to gracefulTimeout for. + ctx, cancel := context.WithTimeout(context.Background(), gracefulTimeout) + defer cancel() + // Doesn't block if no connections, but will otherwise gracefulTimeout + // until the timeout deadline. + errorShutdown := srv.Shutdown(ctx) + + if errorShutdown != nil { + println(errorShutdown.Error()) + } + // Optionally, you could run srv.Shutdown in a goroutine and block on + // <-ctx.Done() if your application should gracefulTimeout for other services + // to finalize based on context cancellation. + log.Println("shutting down") + os.Exit(0) +} + +func Home(w http.ResponseWriter, r *http.Request) { + clientIp, _, errorSplitHostPort := net.SplitHostPort(r.RemoteAddr) + if errorSplitHostPort != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + allow, errorAllowClient := nodedb.AllowClient(clientIpFile, clientIp) + + if errorAllowClient != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorAllowClient.Error()) + return + } + + if !*allow { + w.WriteHeader(http.StatusForbidden) + _, _ = fmt.Fprintf(w, "Your IP Address %s is not allow", clientIp) + return + } + + client := hcloud.NewClient(hcloud.WithToken(accessToken)) + + _context := context.Background() + + var _servers []nodedb.HetznerDto + + var _listServers []hcloud.Server + + _listOptions := hcloud.ServerListOpts{ + ListOpts: hcloud.ListOpts{ + PerPage: 200, + }, + } + + // to be tested + servers, _, errorList := client.Server.List(_context, _listOptions) + if errorList != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorSplitHostPort.Error()) + return + } + + // append the current page's droplets to our list + for _, server := range servers { + _listServers = append(_listServers, *server) + } + + for _, server := range _listServers { + _publicIPv6Address := server.PublicNet.IPv6.IP.String() + _publicIPv4Address := server.PublicNet.IPv4.IP.String() + _region := "" + var _privateIp []string + + if server.PrivateNet != nil { + for _, privateNet := range server.PrivateNet { + _privateIp = append(_privateIp, privateNet.IP.String()) + } + } + + if server.Datacenter != nil { + _region = server.Datacenter.Name + } + + _servers = append(_servers, nodedb.HetznerDto{ + Id: server.ID, + Region: _region, + Name: server.Name, + PrivateIP: _privateIp, + PublicIPv4: _publicIPv4Address, + PublicIPv6: _publicIPv6Address, + }) + } + + _json, errorMarshalIndent := json.MarshalIndent(_servers, "", " ") + + if errorMarshalIndent != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = fmt.Fprintln(w, errorMarshalIndent.Error()) + return + } + + w.WriteHeader(http.StatusOK) + w.Header().Add("Content-Type", "application/json") + _, _ = w.Write(_json) +} diff --git a/devops/nodedb-rest-api/configuration.json b/devops/nodedb-rest-api/configuration.json new file mode 100644 index 0000000..4fa2e3a --- /dev/null +++ b/devops/nodedb-rest-api/configuration.json @@ -0,0 +1,9 @@ +{ + "homeUri": "http://127.0.0.1:8080", + "redirectUri": "http://127.0.0.1:8080/callback", + "digitalOcean": { + "oauthUrl": "https://cloud.digitalocean.com/v1/oauth", + "clientId": "", + "clientSecret": "" + } +} diff --git a/devops/nodedb-rest-api/go.mod b/devops/nodedb-rest-api/go.mod new file mode 100644 index 0000000..044fb79 --- /dev/null +++ b/devops/nodedb-rest-api/go.mod @@ -0,0 +1,42 @@ +module nodedb-rest-api + +go 1.19 + +require ( + github.com/aws/aws-sdk-go-v2 v1.16.14 + github.com/aws/aws-sdk-go-v2/config v1.17.5 + github.com/aws/aws-sdk-go-v2/credentials v1.12.18 + github.com/aws/aws-sdk-go-v2/service/ec2 v1.54.4 + github.com/digitalocean/godo v1.83.0 + github.com/gorilla/mux v1.8.0 + github.com/hetznercloud/hcloud-go v1.35.2 + golang.org/x/exp v0.0.0-20220907003533-145caa8ea1d0 +) + +require ( + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 // indirect + github.com/aws/smithy-go v1.13.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.26.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + golang.org/x/net v0.0.0-20220412020605-290c469a71a5 // indirect + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect +) diff --git a/devops/nodedb-rest-api/pkg/nodedb/dto.go b/devops/nodedb-rest-api/pkg/nodedb/dto.go new file mode 100644 index 0000000..8fcfad7 --- /dev/null +++ b/devops/nodedb-rest-api/pkg/nodedb/dto.go @@ -0,0 +1,48 @@ +package nodedb + +type ConfigurationDto struct { + RedirectUri string `json:"redirectUri"` + HomeUri string `json:"homeUri"` + DigitalOcean DigitalOceanOAuth `json:"digitalOcean"` +} + +type DigitalOceanDto struct { + Id int `json:"id"` + Region string `json:"region"` + Name string `json:"name"` + PublicIPv4 string `json:"public_ip_v4"` + PublicIPv6 string `json:"public_ip_v6"` + PrivateIPv4 string `json:"private_ip_v4"` + Tags []string `json:"tags"` +} + +type DigitalOceanOAuth struct { + OAuthUrl string `json:"oauthUrl"` + ClientId string `json:"clientId"` + ClientSecret string `json:"clientSecret"` +} + +type DigitalOceanOAuthToken struct { + AccessToken string `json:"access_token"` + Bearer string `json:"bearer"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` +} + +type Ec2Dto struct { + TagName string `json:"tag_name"` + InstanceId string `json:"instance_id"` + PublicIP string `json:"public_ip"` + PrivateIP string `json:"private_ip"` + Region string `json:"region"` +} + +type HetznerDto struct { + Id int `json:"id"` + Region string `json:"region"` + Name string `json:"name"` + PublicIPv4 string `json:"public_ip_v4"` + PublicIPv6 string `json:"public_ip_v6"` + PrivateIP []string `json:"private_ip"` +} diff --git a/devops/nodedb-rest-api/pkg/nodedb/utils.go b/devops/nodedb-rest-api/pkg/nodedb/utils.go new file mode 100644 index 0000000..28d174d --- /dev/null +++ b/devops/nodedb-rest-api/pkg/nodedb/utils.go @@ -0,0 +1,121 @@ +package nodedb + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "github.com/aws/aws-sdk-go-v2/aws" + "golang.org/x/exp/slices" + "log" + "net" + "net/http" + "os" + "time" +) + +func ConfigurationServer(serverPort int, serverCertificate string, serverPrivateKey string, serverMtls string) *http.Server { + if serverCertificate != "" && serverPrivateKey != "" { + var config *tls.Config + if serverMtls != "" { + mtlsCertificate, errorReadFile := os.ReadFile(serverMtls) + if errorReadFile != nil { + log.Println(errorReadFile.Error()) + } else { + certPool := x509.NewCertPool() + certPool.AppendCertsFromPEM(mtlsCertificate) + config = &tls.Config{ + ClientCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + } + } + } + return &http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", serverPort), + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + TLSConfig: config, + } + } else { + return &http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", serverPort), + // Good practice to set timeouts to avoid Slowloris attacks. + WriteTimeout: time.Second * 15, + ReadTimeout: time.Second * 15, + IdleTimeout: time.Second * 60, + } + } +} + +func CheckIPv4AddressType(ip string) bool { + if net.ParseIP(ip) == nil { + return false + } + for i := 0; i < len(ip); i++ { + switch ip[i] { + case '.': + return true + } + } + return false +} + +func InfoIP(hasTLS bool, serverPort int) { + ifaces, errorInterfaces := net.Interfaces() + if errorInterfaces != nil { + log.Println(errorInterfaces.Error()) + } else { + for _, i := range ifaces { + addrs, errorAddrs := i.Addrs() + if errorAddrs != nil { + log.Println(errorAddrs.Error()) + } else { + // handle err + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if CheckIPv4AddressType(ip.String()) { + if (hasTLS && serverPort == 443) || (!hasTLS && serverPort == 80) { + if hasTLS { + log.Println(fmt.Sprintf("Listenning %s://%s", "https", ip.String())) + } else { + log.Println(fmt.Sprintf("Listenning %s://%s", "http", ip.String())) + } + } else { + if hasTLS { + log.Println(fmt.Sprintf("Listenning %s://%s:%d", "https", ip.String(), serverPort)) + } else { + log.Println(fmt.Sprintf("Listenning %s://%s:%d", "http", ip.String(), serverPort)) + } + } + } + } + } + } + } +} + +func AllowClient(clientIpFile string, clientIp string) (*bool, error) { + clientIpContent, errorReadFile := os.ReadFile(clientIpFile) + + if errorReadFile != nil { + return nil, errorReadFile + } + + var allowIps []string + + errorUnmarshal := json.Unmarshal(clientIpContent, &allowIps) + + if errorUnmarshal != nil { + return nil, errorUnmarshal + } + + return aws.Bool(slices.Contains(allowIps, clientIp)), nil +}