more file structure
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -1 +1,5 @@
|
||||
data.sqlite
|
||||
data.sqlite
|
||||
data.sqlite-journal
|
||||
.env
|
||||
vctp
|
||||
vctp.log
|
43
api/resource/common/handler.go
Normal file
43
api/resource/common/handler.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Display some information about this API for the default page
|
||||
// TODO - static fileserver with docs
|
||||
func HomeLink(w http.ResponseWriter, r *http.Request) {
|
||||
//w.WriteHeader(http.StatusNotImplemented)
|
||||
fmt.Fprintf(w, "VM Chargeback Tracking Program. API interface only. See Nathan Coad (nathan.coad@dell.com) for further details. ")
|
||||
}
|
||||
|
||||
// Display error message for invalid requests
|
||||
func HandleNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
ip, err := IPFromRequest(r)
|
||||
if err != nil {
|
||||
fmt.Println("Error", err)
|
||||
http.Error(w, "VM Chargeback Tracking Program.. Invalid Path Specified.", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("Request from IP %s\n", ip.String())
|
||||
http.Error(w, "VM Chargeback Tracking Program. Invalid Path Specified.", http.StatusNotFound)
|
||||
// TODO - investigate rate limiting for invalid requests
|
||||
}
|
||||
|
||||
// IPFromRequest extracts the user IP address from req, if present.
|
||||
// @see https://blog.golang.org/context/userip/userip.go
|
||||
func IPFromRequest(req *http.Request) (net.IP, error) {
|
||||
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
|
||||
}
|
||||
|
||||
userIP := net.ParseIP(ip)
|
||||
if userIP == nil {
|
||||
return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)
|
||||
}
|
||||
return userIP, nil
|
||||
}
|
32
api/resource/vm/handler.go
Normal file
32
api/resource/vm/handler.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
// TODO godoc
|
||||
func VmCreateHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
reqBody, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(w, "Invalid data received")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
slog.Debug("received create request", "body", string(reqBody))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Create Request (%d): %v\n", len(reqBody), string(reqBody))
|
||||
}
|
||||
|
||||
// TODO godoc
|
||||
func VmRemoveHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprintf(w, "Remove Request: %v\n", vars)
|
||||
}
|
58
api/resource/vm/model.go
Normal file
58
api/resource/vm/model.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package vm
|
||||
|
||||
import "time"
|
||||
|
||||
type CloudEventReceived struct {
|
||||
ID string `json:"id"`
|
||||
Specversion string `json:"specversion"`
|
||||
Source string `json:"source"`
|
||||
Type string `json:"type"`
|
||||
Time time.Time `json:"time"`
|
||||
Data struct {
|
||||
ChainID int `json:"ChainId"`
|
||||
ChangeTag string `json:"ChangeTag"`
|
||||
ComputeResource struct {
|
||||
ComputeResource struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"ComputeResource"`
|
||||
Name string `json:"Name"`
|
||||
} `json:"ComputeResource"`
|
||||
CreatedTime time.Time `json:"CreatedTime"`
|
||||
Datacenter struct {
|
||||
Datacenter struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Datacenter"`
|
||||
Name string `json:"Name"`
|
||||
} `json:"Datacenter"`
|
||||
Ds interface{} `json:"Ds"`
|
||||
Dvs interface{} `json:"Dvs"`
|
||||
FullFormattedMessage string `json:"FullFormattedMessage"`
|
||||
Host struct {
|
||||
Host struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Host"`
|
||||
Name string `json:"Name"`
|
||||
} `json:"Host"`
|
||||
Key int `json:"Key"`
|
||||
Net interface{} `json:"Net"`
|
||||
SrcTemplate struct {
|
||||
Name string `json:"Name"`
|
||||
VM struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Vm"`
|
||||
} `json:"SrcTemplate"`
|
||||
Template bool `json:"Template"`
|
||||
UserName string `json:"UserName"`
|
||||
VM struct {
|
||||
Name string `json:"Name"`
|
||||
VM struct {
|
||||
Type string `json:"Type"`
|
||||
Value string `json:"Value"`
|
||||
} `json:"Vm"`
|
||||
} `json:"Vm"`
|
||||
} `json:"data"`
|
||||
}
|
25
api/router/router.go
Normal file
25
api/router/router.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"vm-ctp/api/resource/common"
|
||||
"vm-ctp/api/resource/vm"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func New() *mux.Router {
|
||||
r := mux.NewRouter()
|
||||
|
||||
// If nothing more specific is requested then just display some version information
|
||||
r.HandleFunc("/", common.HomeLink)
|
||||
|
||||
s := r.PathPrefix("/api").Subrouter()
|
||||
s.HandleFunc("/event/vm/create", vm.VmCreateHandler).Methods("POST") // receive VM creation event from Direktiv
|
||||
s.HandleFunc("/event/vm/remove", vm.VmRemoveHandler).Methods("POST") // receive VM creation event from Direktiv
|
||||
|
||||
// Not found handler
|
||||
r.NotFoundHandler = http.HandlerFunc(common.HandleNotFound)
|
||||
|
||||
return r
|
||||
}
|
30
cert.pem
Normal file
30
cert.pem
Normal file
@@ -0,0 +1,30 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIFIjCCAwqgAwIBAgIRALRk49JcstjX/i+y8wHc4KAwDQYJKoZIhvcNAQELBQAw
|
||||
DzENMAsGA1UEChMERFRNUzAeFw0yNDA5MTExMDU0NDBaFw0yNTA5MTExMDU0NDBa
|
||||
MA8xDTALBgNVBAoTBERUTVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
|
||||
AQDVVlFCBCo5uJ4+D33Y9iJjz5PbW76ubMSWf+ndBTkUF9Psw+eyzkR1b7EVT2/c
|
||||
eJfAK2rE6vTAajf9JItRMLCQXPVRf8ecmg/NcVhwuNSKG86TDqGfbNBmBnSZQhEu
|
||||
JSfMoZvUiYtKd8JY9o8Y7n9BaU3cM0lg57+2TvLsF1WEY4DKr2P46AmhoMnqgC2n
|
||||
1hokgNmPIuxfWk1N+zWkw0Kec2KiroB4hKiwSIcidR6YaR5/8Iiuorg2O4rhNx1D
|
||||
lFGHUAIPTpCrpQjoMD3rHcnpGjK6NksO8gFOgjjd9XKpznGk2+bhPxTA9mwoL+qx
|
||||
E/MdTAjKn+fJB2FYduT8ZlkFSu98WGjZr5/UT72fDemNVMNGfzWVEGgWQCRSHvvI
|
||||
KzPb8A8GLaSj0CH/jww1KyXFmu8adU7Spg/ZHU9rStgdrGfGmtx6MXLE1a87mEQl
|
||||
M/suEP7/ikK570FSJh1kop1MdDNPaMxtsRXfGws1JVWMhPzlmReC9jkAbfW5axfd
|
||||
Nh4jYJm18rqlQR7JDPjusD4zdI+mesoiI45vaG0/b3F63pKYLuREuE0RIpimQlKV
|
||||
A0+X2RXk8+h9Xpc3Z1pVoHHcoQ3Jw+JM0KScFncaBxczqSo/EQzw4LE9XYyz7Rks
|
||||
qpU3WzvcVimEpj6flpqobGI63kf1rSkeCaIDLdgUlUJTxQIDAQABo3kwdzAOBgNV
|
||||
HQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB
|
||||
/zAdBgNVHQ4EFgQUAa1rnwMtehNK2bVPXKivXzNHIKYwIAYDVR0RBBkwF4IPbmF0
|
||||
aG1icDE0LmxvY2FshwQKAAGhMA0GCSqGSIb3DQEBCwUAA4ICAQBenuJYnqTcELUP
|
||||
scHkrVhH0zpmejRk55qBAxIpfR4XFklKr1lt3aHTs3dDbfS9t3ni/S2RVVhVtgWm
|
||||
pXt3AteYCkA8CbuhSBirtVrDWi/NT6ClZHRgCcfoZzE5uh9HuQOQpGg2iRby/tnG
|
||||
FU2o8KpYETcCEEuvhlusIg7FTueqCMCvbjsZ8j9PY5nZ0JZmAAqS2g4MR0zqFBfj
|
||||
wamx3gXLupMdOdcAnqlVkc6UxkWyLvhcy1kpl+E+xzFoOsC0XCLSwFdM2gIB21yd
|
||||
HcVMOnP38t+ijrErv5prsczuUhP0T3q/XBr5wzc4OBIQYbqjzhP67JaVZ6HU8UWj
|
||||
GTuUuuZDwSTBoIJ0sDxmoMt9z2j7xFvSeMVKf13CRTR2s5QLPX/1AIyANg88xjQM
|
||||
jJPGyiaBCYZZJbjH2SwwKuyqOaph8yz140QgxMNi6yymbruFBk9Gj9b6jDklFSM5
|
||||
kHpmC4Gs8apL57odLnwl/Bf6A51z6BtZe6vzKl33ED5Zq0AlgIh3cICsXMwBTB+1
|
||||
6xGFVpyqjKLe4p0laDrJh26O2wCjZdztU71pTIKU+UNhxCssXmy8Dw7qm82xgPxK
|
||||
EENv6jK8jArVAyCJcmLxzhT2cvaN0NnqVRc6zDvY3K+KzG1Q7AcpJGeaM0BZym7l
|
||||
9so3Qdv2RdPWtgdNX4EMDwxTUaeD6g==
|
||||
-----END CERTIFICATE-----
|
157
cmd/api/main.go
157
cmd/api/main.go
@@ -1 +1,158 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
"vm-ctp/api/router"
|
||||
utils "vm-ctp/internal/utils"
|
||||
|
||||
"github.com/gorilla/handlers"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
// For build numbers, from https://blog.kowalczyk.info/article/vEja/embedding-build-number-in-go-executable.html
|
||||
var sha1ver string // sha1 revision used to build the program
|
||||
var buildTime string // when the executable was built
|
||||
var wait time.Duration
|
||||
var bindDisableTls bool
|
||||
|
||||
func main() {
|
||||
// Load data from environment file
|
||||
envFilename := utils.GetFilePath(".env")
|
||||
err := godotenv.Load(envFilename)
|
||||
if err != nil {
|
||||
panic("Error loading .env file")
|
||||
}
|
||||
|
||||
// Open connection to logfile
|
||||
// From https://ispycode.com/GO/Logging/Logging-to-multiple-destinations
|
||||
logFile := os.Getenv("LOG_FILE")
|
||||
if logFile == "" {
|
||||
logFile = "./vctp.log"
|
||||
}
|
||||
logfileWriter, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||
if err != nil {
|
||||
fmt.Printf("Unable to write logfile '%s' : '%s'\n", logFile, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug, AddSource: true})
|
||||
slog.SetDefault(slog.New(h))
|
||||
|
||||
//log.SetOutput(logfileWriter)
|
||||
//log.Printf("vCTP starting execution. Built on %s from sha1 %s. Runtime %s\n", buildTime, sha1ver, runtime.Version())
|
||||
|
||||
r := router.New()
|
||||
|
||||
// Log everything to stdout in Apache Common Log Format
|
||||
var loggedRouter http.Handler
|
||||
loggedRouter = handlers.LoggingHandler(logfileWriter, r)
|
||||
|
||||
// Set some options for TLS
|
||||
tlsConfig := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384, tls.CurveP256},
|
||||
PreferServerCipherSuites: true,
|
||||
InsecureSkipVerify: true,
|
||||
CipherSuites: []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
|
||||
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
|
||||
},
|
||||
}
|
||||
|
||||
// Determine bind IP
|
||||
bindIP := os.Getenv("BIND_IP")
|
||||
if bindIP == "" {
|
||||
bindIP = utils.GetOutboundIP().String()
|
||||
}
|
||||
// Determine bind port
|
||||
bindPort := os.Getenv("BIND_PORT")
|
||||
if bindPort == "" {
|
||||
bindPort = "9443"
|
||||
}
|
||||
bindAddress := fmt.Sprint(bindIP, ":", bindPort)
|
||||
slog.Info("Will listen on address", "ip", bindIP, "port", bindPort)
|
||||
|
||||
// Determine bind disable TLS
|
||||
bindDisableTlsEnv := os.Getenv("BIND_DISABLE_TLS")
|
||||
if bindDisableTlsEnv == "true" {
|
||||
bindDisableTls = true
|
||||
}
|
||||
|
||||
// Get file names for TLS cert/key
|
||||
tlsCertFilename := os.Getenv("TLS_CERT_FILE")
|
||||
if tlsCertFilename != "" {
|
||||
tlsCertFilename = utils.GetFilePath(tlsCertFilename)
|
||||
} else {
|
||||
tlsCertFilename = "./cert.pem"
|
||||
}
|
||||
|
||||
tlsKeyFilename := os.Getenv("TLS_KEY_FILE")
|
||||
if tlsKeyFilename != "" {
|
||||
tlsKeyFilename = utils.GetFilePath(tlsKeyFilename)
|
||||
} else {
|
||||
tlsKeyFilename = "./privkey.pem"
|
||||
}
|
||||
|
||||
// Generate certificate if required
|
||||
if !(utils.FileExists(tlsCertFilename) && utils.FileExists(tlsKeyFilename)) {
|
||||
slog.Warn("Specified TLS certificate or private key do not exist", "certificate", tlsCertFilename, "tls-key", tlsKeyFilename)
|
||||
utils.GenerateCerts(tlsCertFilename, tlsKeyFilename)
|
||||
}
|
||||
|
||||
// Configure the http server
|
||||
srv := &http.Server{
|
||||
Addr: bindAddress,
|
||||
// Good practice to set timeouts to avoid Slowloris attacks.
|
||||
WriteTimeout: time.Second * 15,
|
||||
ReadTimeout: time.Second * 15,
|
||||
IdleTimeout: time.Second * 60,
|
||||
Handler: loggedRouter, // Pass our instance of gorilla/mux in.
|
||||
TLSConfig: tlsConfig,
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
}
|
||||
slog.Info("Started listening on", "bindAddress", bindAddress)
|
||||
|
||||
// Run our server in a goroutine so that it doesn't block
|
||||
// Also decide whether to start a TLS or plain http server
|
||||
go func() {
|
||||
if bindDisableTls {
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
} else {
|
||||
if err := srv.ListenAndServeTLS(tlsCertFilename, tlsKeyFilename); err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
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 wait for.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), wait)
|
||||
defer cancel()
|
||||
// Doesn't block if no connections, but will otherwise wait
|
||||
// until the timeout deadline.
|
||||
srv.Shutdown(ctx)
|
||||
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
||||
// <-ctx.Done() if your application should wait for other services
|
||||
// to finalize based on context cancellation.
|
||||
slog.Info("shutting down")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
Binary file not shown.
13
go.mod
13
go.mod
@@ -1,3 +1,12 @@
|
||||
module vm-cbp
|
||||
module vm-ctp
|
||||
|
||||
go 1.21.6
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/handlers v1.5.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
)
|
||||
|
||||
require github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
|
10
go.sum
Normal file
10
go.sum
Normal file
@@ -0,0 +1,10 @@
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
|
||||
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
139
internal/utils/certOperations.go
Normal file
139
internal/utils/certOperations.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func GenerateCerts(tlsCert string, tlsKey string) {
|
||||
// @see https://shaneutt.com/blog/golang-ca-and-signed-cert-go/
|
||||
// @see https://golang.org/src/crypto/tls/generate_cert.go
|
||||
validFrom := ""
|
||||
validFor := 365 * 24 * time.Hour
|
||||
isCA := true
|
||||
|
||||
// Get the hostname
|
||||
hostname, err := os.Hostname()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Check that the directory exists
|
||||
relativePath := filepath.Dir(tlsCert)
|
||||
log.Printf("GenerateCerts relative path for file creation is '%s'\n", relativePath)
|
||||
_, err = os.Stat(relativePath)
|
||||
if os.IsNotExist(err) {
|
||||
log.Printf("Certificate path does not exist, creating %s before generating certificate\n", relativePath)
|
||||
os.MkdirAll(relativePath, os.ModePerm)
|
||||
}
|
||||
|
||||
// Generate a private key
|
||||
priv, err := rsa.GenerateKey(rand.Reader, rsaBits)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate private key: %v", err)
|
||||
}
|
||||
|
||||
var notBefore time.Time
|
||||
if len(validFrom) == 0 {
|
||||
notBefore = time.Now()
|
||||
} else {
|
||||
notBefore, err = time.Parse("Jan 2 15:04:05 2006", validFrom)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse creation date: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
notAfter := notBefore.Add(validFor)
|
||||
|
||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to generate serial number: %v", err)
|
||||
}
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: serialNumber,
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"DTMS"},
|
||||
},
|
||||
NotBefore: notBefore,
|
||||
NotAfter: notAfter,
|
||||
|
||||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
}
|
||||
|
||||
template.DNSNames = append(template.DNSNames, hostname)
|
||||
|
||||
// Add in all the non-local IPs
|
||||
ifaces, err := net.Interfaces()
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error enumerating interfaces: %v\n", err)
|
||||
}
|
||||
|
||||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
log.Printf("Oops: %v\n", err)
|
||||
}
|
||||
|
||||
for _, address := range addrs {
|
||||
// check the address type and if it is not a loopback then add it to the list
|
||||
if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
template.IPAddresses = append(template.IPAddresses, ipnet.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isCA {
|
||||
template.IsCA = true
|
||||
template.KeyUsage |= x509.KeyUsageCertSign
|
||||
}
|
||||
|
||||
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create certificate: %v", err)
|
||||
}
|
||||
|
||||
certOut, err := os.Create(tlsCert)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open %s for writing: %v", tlsCert, err)
|
||||
}
|
||||
if err := pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
|
||||
log.Fatalf("Failed to write data to %s: %v", tlsCert, err)
|
||||
}
|
||||
if err := certOut.Close(); err != nil {
|
||||
log.Fatalf("Error closing %s: %v", tlsCert, err)
|
||||
}
|
||||
log.Printf("wrote %s\n", tlsCert)
|
||||
|
||||
keyOut, err := os.OpenFile(tlsKey, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open %s for writing: %v", tlsKey, err)
|
||||
return
|
||||
}
|
||||
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to marshal private key: %v", err)
|
||||
}
|
||||
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
|
||||
log.Fatalf("Failed to write data to %s: %v", tlsKey, err)
|
||||
}
|
||||
if err := keyOut.Close(); err != nil {
|
||||
log.Fatalf("Error closing %s: %v", tlsKey, err)
|
||||
}
|
||||
log.Printf("wrote %s\n", tlsKey)
|
||||
}
|
55
internal/utils/utils.go
Normal file
55
internal/utils/utils.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
const rsaBits = 4096
|
||||
|
||||
func GetFilePath(path string) string {
|
||||
// Check for empty filename
|
||||
if len(path) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// check if filename exists
|
||||
if _, err := os.Stat(path); os.IsNotExist((err)) {
|
||||
slog.Info("File '%s' not found, searching in same directory as binary", path)
|
||||
// if not, check that it exists in the same directory as the currently executing binary
|
||||
ex, err2 := os.Executable()
|
||||
if err2 != nil {
|
||||
slog.Error("Error determining binary path : '%s'", err)
|
||||
return ""
|
||||
}
|
||||
binaryPath := filepath.Dir(ex)
|
||||
path = filepath.Join(binaryPath, path)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// Get preferred outbound ip of this machine
|
||||
// @see https://stackoverflow.com/questions/23558425/how-do-i-get-the-local-ip-address-in-go
|
||||
func GetOutboundIP() net.IP {
|
||||
conn, err := net.Dial("udp", "8.8.8.8:80")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
return localAddr.IP
|
||||
}
|
||||
|
||||
// Check if a file exists from https://stackoverflow.com/questions/12518876/how-to-check-if-a-file-exists-in-go
|
||||
func FileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
52
privkey.pem
Normal file
52
privkey.pem
Normal file
@@ -0,0 +1,52 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
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
|
||||
Lp15oQrT2tvdis2K06zLyv9LJUFsvA==
|
||||
-----END PRIVATE KEY-----
|
@@ -14,20 +14,13 @@ type Vm struct {
|
||||
Vcenter sql.NullString
|
||||
CreationTime sql.NullString
|
||||
DeletionTime sql.NullString
|
||||
TinRpTime sql.NullFloat64
|
||||
BronzeRpTime sql.NullFloat64
|
||||
SilverRpTime sql.NullFloat64
|
||||
GoldRpTime sql.NullFloat64
|
||||
ResourcePool sql.NullString
|
||||
VmType sql.NullString
|
||||
PoweredOnPct sql.NullInt64
|
||||
Datacenter sql.NullString
|
||||
Cluster sql.NullString
|
||||
Folder sql.NullString
|
||||
ProvisionedDisk sql.NullFloat64
|
||||
InitialVcpus sql.NullInt64
|
||||
AvgVcpus sql.NullFloat64
|
||||
InitialRam sql.NullInt64
|
||||
AvgRam sql.NullFloat64
|
||||
SrmPlaceholder sql.NullInt64
|
||||
}
|
||||
|
@@ -12,22 +12,45 @@ import (
|
||||
|
||||
const create = `-- name: Create :execresult
|
||||
insert into "vm" (
|
||||
"Name", "Vcenter"
|
||||
"Name", "Vcenter", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
|
||||
)
|
||||
values(?, ?)
|
||||
values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`
|
||||
|
||||
type CreateParams struct {
|
||||
Name sql.NullString
|
||||
Vcenter sql.NullString
|
||||
Name sql.NullString
|
||||
Vcenter sql.NullString
|
||||
CreationTime sql.NullString
|
||||
ResourcePool sql.NullString
|
||||
VmType sql.NullString
|
||||
Datacenter sql.NullString
|
||||
Cluster sql.NullString
|
||||
Folder sql.NullString
|
||||
ProvisionedDisk sql.NullFloat64
|
||||
InitialVcpus sql.NullInt64
|
||||
InitialRam sql.NullInt64
|
||||
SrmPlaceholder sql.NullInt64
|
||||
}
|
||||
|
||||
func (q *Queries) Create(ctx context.Context, arg CreateParams) (sql.Result, error) {
|
||||
return q.db.ExecContext(ctx, create, arg.Name, arg.Vcenter)
|
||||
return q.db.ExecContext(ctx, create,
|
||||
arg.Name,
|
||||
arg.Vcenter,
|
||||
arg.CreationTime,
|
||||
arg.ResourcePool,
|
||||
arg.VmType,
|
||||
arg.Datacenter,
|
||||
arg.Cluster,
|
||||
arg.Folder,
|
||||
arg.ProvisionedDisk,
|
||||
arg.InitialVcpus,
|
||||
arg.InitialRam,
|
||||
arg.SrmPlaceholder,
|
||||
)
|
||||
}
|
||||
|
||||
const get = `-- name: Get :one
|
||||
select Id, Name, Vcenter, CreationTime, DeletionTime, TinRpTime, BronzeRpTime, SilverRpTime, GoldRpTime, ResourcePool, VmType, PoweredOnPct, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, AvgVcpus, InitialRam, AvgRam, SrmPlaceholder from "vm" where "Name" = ?
|
||||
select Id, Name, Vcenter, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder from "vm" where "Name" = ?
|
||||
`
|
||||
|
||||
func (q *Queries) Get(ctx context.Context, name sql.NullString) (Vm, error) {
|
||||
@@ -39,28 +62,21 @@ func (q *Queries) Get(ctx context.Context, name sql.NullString) (Vm, error) {
|
||||
&i.Vcenter,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.TinRpTime,
|
||||
&i.BronzeRpTime,
|
||||
&i.SilverRpTime,
|
||||
&i.GoldRpTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.PoweredOnPct,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.AvgVcpus,
|
||||
&i.InitialRam,
|
||||
&i.AvgRam,
|
||||
&i.SrmPlaceholder,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const list = `-- name: List :many
|
||||
select Id, Name, Vcenter, CreationTime, DeletionTime, TinRpTime, BronzeRpTime, SilverRpTime, GoldRpTime, ResourcePool, VmType, PoweredOnPct, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, AvgVcpus, InitialRam, AvgRam, SrmPlaceholder from "vm" order by "Name"
|
||||
select Id, Name, Vcenter, CreationTime, DeletionTime, ResourcePool, VmType, Datacenter, Cluster, Folder, ProvisionedDisk, InitialVcpus, InitialRam, SrmPlaceholder from "vm" order by "Name"
|
||||
`
|
||||
|
||||
func (q *Queries) List(ctx context.Context) ([]Vm, error) {
|
||||
@@ -78,21 +94,14 @@ func (q *Queries) List(ctx context.Context) ([]Vm, error) {
|
||||
&i.Vcenter,
|
||||
&i.CreationTime,
|
||||
&i.DeletionTime,
|
||||
&i.TinRpTime,
|
||||
&i.BronzeRpTime,
|
||||
&i.SilverRpTime,
|
||||
&i.GoldRpTime,
|
||||
&i.ResourcePool,
|
||||
&i.VmType,
|
||||
&i.PoweredOnPct,
|
||||
&i.Datacenter,
|
||||
&i.Cluster,
|
||||
&i.Folder,
|
||||
&i.ProvisionedDisk,
|
||||
&i.InitialVcpus,
|
||||
&i.AvgVcpus,
|
||||
&i.InitialRam,
|
||||
&i.AvgRam,
|
||||
&i.SrmPlaceholder,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
|
@@ -6,6 +6,6 @@ select * from "vm" order by "Name";
|
||||
|
||||
-- name: Create :execresult
|
||||
insert into "vm" (
|
||||
"Name", "Vcenter"
|
||||
"Name", "Vcenter", "CreationTime", "ResourcePool", "VmType", "Datacenter", "Cluster", "Folder", "ProvisionedDisk", "InitialVcpus", "InitialRam", "SrmPlaceholder"
|
||||
)
|
||||
values(?, ?);
|
||||
values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
@@ -4,20 +4,13 @@ CREATE TABLE IF NOT EXISTS "vm" (
|
||||
"Vcenter" TEXT,
|
||||
"CreationTime" TEXT,
|
||||
"DeletionTime" TEXT,
|
||||
"TinRpTime" REAL,
|
||||
"BronzeRpTime" REAL,
|
||||
"SilverRpTime" REAL,
|
||||
"GoldRpTime" REAL,
|
||||
"ResourcePool" TEXT,
|
||||
"VmType" TEXT,
|
||||
"PoweredOnPct" INTEGER,
|
||||
"Datacenter" TEXT,
|
||||
"Cluster" TEXT,
|
||||
"Folder" TEXT,
|
||||
"ProvisionedDisk" REAL,
|
||||
"InitialVcpus" INTEGER,
|
||||
"AvgVcpus" REAL,
|
||||
"InitialRam" INTEGER,
|
||||
"AvgRam" REAL,
|
||||
"SrmPlaceholder" INTEGER
|
||||
);
|
Reference in New Issue
Block a user