Initial commit
commit
080c1a3d06
@ -0,0 +1,3 @@
|
||||
out
|
||||
examples/*
|
||||
!examples/service.toml
|
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright © 2019 eater
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
@ -0,0 +1,40 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type ZeroooConfig struct {
|
||||
Location string `toml:"location"`
|
||||
Endpoint string `toml:"endpoint"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Zerooo ZeroooConfig `toml:"zerooo"`
|
||||
}
|
||||
|
||||
func LoadConfig(path string) (*Config, error) {
|
||||
config := Config{}
|
||||
_, err := toml.DecodeFile(path, &config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
func CreateConfig(path string, cfg *Config) {
|
||||
conf, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
Check(err, "Couldn't open %q, err: %s", path, err)
|
||||
encoder := toml.NewEncoder(conf)
|
||||
log.Printf("ZeroooConfig: %#v", cfg)
|
||||
err = encoder.Encode(cfg)
|
||||
if cfg.Zerooo.Location[0] != '/' {
|
||||
cfg.Zerooo.Location = filepath.Join(filepath.Dir(path), cfg.Zerooo.Location)
|
||||
}
|
||||
|
||||
Check(err, "Couldn't parse %q, err: %s", path, err)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
[zerooo]
|
||||
location = "/etc/zerooo"
|
||||
endpoint = "https://zer.ooo"
|
@ -0,0 +1,217 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
import "golang.org/x/crypto/blowfish"
|
||||
|
||||
type Status int
|
||||
|
||||
const (
|
||||
OK Status = iota
|
||||
WAITING
|
||||
ERROR
|
||||
)
|
||||
|
||||
type BaseMessage struct {
|
||||
Signature string
|
||||
}
|
||||
|
||||
type httpServer struct {
|
||||
manager *Manager
|
||||
status Status
|
||||
}
|
||||
|
||||
type CreateCSRRequest struct {
|
||||
*BaseMessage
|
||||
Hostname string
|
||||
}
|
||||
|
||||
type CreateCSRResponse struct {
|
||||
*BaseMessage
|
||||
CSR string `json:"csr"`
|
||||
}
|
||||
|
||||
type UpdateOpenVPNConfigRequest struct {
|
||||
*BaseMessage
|
||||
Config string
|
||||
}
|
||||
|
||||
type DeliverCertificateRequest struct {
|
||||
*BaseMessage
|
||||
Certificate string
|
||||
}
|
||||
|
||||
func NewHttpServer(manager *Manager) *httpServer {
|
||||
return &httpServer{manager, WAITING}
|
||||
}
|
||||
|
||||
func (it *httpServer) Start() {
|
||||
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
|
||||
writer.WriteHeader(200)
|
||||
writer.Write([]byte("Don't"))
|
||||
})
|
||||
|
||||
http.HandleFunc("/create-csr", func(writer http.ResponseWriter, request *http.Request) {
|
||||
req := &CreateCSRRequest{}
|
||||
err := it.verifyRequest(request, req)
|
||||
|
||||
if err != nil {
|
||||
writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
csr, err := it.manager.CreateCSR(req.Hostname)
|
||||
if err != nil {
|
||||
writer.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
it.writeResponse(writer, CreateCSRResponse{
|
||||
CSR: string(csr),
|
||||
})
|
||||
})
|
||||
|
||||
http.HandleFunc("/deliver-crt", func(writer http.ResponseWriter, request *http.Request) {
|
||||
req := &DeliverCertificateRequest{}
|
||||
err := it.verifyRequest(request, req)
|
||||
if err != nil {
|
||||
writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
err = it.manager.UpdateCertificate(req.Certificate)
|
||||
if err != nil {
|
||||
writer.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
it.manager.openVPN.Start()
|
||||
})
|
||||
|
||||
http.HandleFunc("/update-openvpn-config", func(writer http.ResponseWriter, request *http.Request) {
|
||||
req := &UpdateOpenVPNConfigRequest{}
|
||||
err := it.verifyRequest(request, req)
|
||||
if err != nil {
|
||||
writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
||||
err = it.manager.openVPN.UpdateConfig(req.Config)
|
||||
if err != nil {
|
||||
writer.WriteHeader(500)
|
||||
return
|
||||
}
|
||||
|
||||
it.manager.openVPN.Restart()
|
||||
})
|
||||
|
||||
http.ListenAndServe(":7864", nil)
|
||||
}
|
||||
|
||||
func (it *httpServer) writeResponse(writer http.ResponseWriter, v interface{}) error {
|
||||
passwordAndIV := make([]byte, 48)
|
||||
_, err := rand.Read(passwordAndIV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encPasswordAndIV, err := rsa.EncryptPKCS1v15(rand.Reader, it.manager.CAPublicKey(), passwordAndIV)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = writer.Write([]byte(hex.EncodeToString(encPasswordAndIV)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password := passwordAndIV[:32]
|
||||
iv := passwordAndIV[32:]
|
||||
|
||||
cipher, err := blowfish.NewSaltedCipher(password, iv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hashedFingerpint := sha256.Sum256(it.manager.GetServerFingerprint())
|
||||
plainSignature, err := rsa.SignPKCS1v15(rand.Reader, it.manager.privateKey, 0, hashedFingerpint[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sign, ok := v.(*BaseMessage)
|
||||
if !ok {
|
||||
return errors.New("Given message can't be signed")
|
||||
}
|
||||
sign.Signature = hex.EncodeToString(plainSignature)
|
||||
|
||||
body, err := json.Marshal(v)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encBody := make([]byte, len(body))
|
||||
cipher.Encrypt(encBody, body)
|
||||
writer.Write([]byte(hex.EncodeToString(encBody)))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (it *httpServer) verifyRequest(r *http.Request, v interface{}) (error) {
|
||||
all, err := ioutil.ReadAll(hex.NewDecoder(r.Body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
encPasswordAndIV := all[:256]
|
||||
encBody := all[256:]
|
||||
|
||||
passwordAndIV, err := rsa.DecryptPKCS1v15(rand.Reader, it.manager.privateKey, encPasswordAndIV)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password := passwordAndIV[:32]
|
||||
iv := passwordAndIV[32:]
|
||||
|
||||
cipher, err := blowfish.NewSaltedCipher(password, iv)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
body := make([]byte, len(encBody))
|
||||
cipher.Decrypt(body, encBody)
|
||||
|
||||
err = json.Unmarshal(body, v)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseReq, ok := v.(*BaseMessage)
|
||||
if !ok {
|
||||
return errors.New("non-base request")
|
||||
}
|
||||
|
||||
signature, err := hex.DecodeString(baseReq.Signature)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fingerprint := it.manager.GetServerFingerprint()
|
||||
fingerprintHashed := sha256.Sum256(fingerprint)
|
||||
err = rsa.VerifyPKCS1v15(it.manager.CAPublicKey(), 0, fingerprintHashed[:], signature)
|
||||
return err
|
||||
}
|
@ -0,0 +1,339 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/md5"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
Config Config
|
||||
privateKey *rsa.PrivateKey
|
||||
openVPN *OpenVPN
|
||||
CA *x509.Certificate
|
||||
HasCertificate bool
|
||||
HasOpenVPNConfig bool
|
||||
}
|
||||
|
||||
func NewManager(cfg Config) *Manager {
|
||||
return &Manager{
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func fileExists(s string) bool {
|
||||
if _, err := os.Stat(s); err == nil {
|
||||
return true
|
||||
} else if os.IsNotExist(err) {
|
||||
return false
|
||||
} else {
|
||||
Check(err, "Failed to check %q, err: %s", s, err)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (it *Manager) GetOpenVPNConfigLocation() string {
|
||||
return it.Config.Zerooo.Location + "/server.conf"
|
||||
}
|
||||
|
||||
func (it *Manager) GetCALocation() string {
|
||||
return it.Config.Zerooo.Location + "/ca.crt"
|
||||
}
|
||||
|
||||
func (it *Manager) GetCertificateLocation() string {
|
||||
return it.Config.Zerooo.Location + "/server.crt"
|
||||
}
|
||||
|
||||
func (it *Manager) GetKeyLocation() string {
|
||||
return it.Config.Zerooo.Location + "/server.key"
|
||||
}
|
||||
|
||||
func (it *Manager) GetDHLocation() string {
|
||||
return it.Config.Zerooo.Location + "/dh2048.pem"
|
||||
}
|
||||
|
||||
func (it *Manager) GetCRLLocation() string {
|
||||
return it.Config.Zerooo.Location + "/crl.pem"
|
||||
}
|
||||
|
||||
func (it *Manager) EnsureFile(path string, generator func()) bool {
|
||||
if !fileExists(path) {
|
||||
generator()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
func (it *Manager) Init() {
|
||||
if !fileExists(it.Config.Zerooo.Location) {
|
||||
os.MkdirAll(it.Config.Zerooo.Location, 0755)
|
||||
}
|
||||
|
||||
generated := it.EnsureFile(it.GetKeyLocation(), func() {
|
||||
it.GenerateKey()
|
||||
})
|
||||
|
||||
if !generated {
|
||||
it.LoadKey()
|
||||
}
|
||||
|
||||
it.EnsureFile(it.GetCALocation(), func() {
|
||||
it.DownloadCA()
|
||||
})
|
||||
|
||||
it.LoadCA()
|
||||
|
||||
it.EnsureFile(it.GetCRLLocation(), func() {
|
||||
it.DownloadCRL()
|
||||
})
|
||||
|
||||
it.EnsureFile(it.GetDHLocation(), func() {
|
||||
it.DownloadDH()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func (it *Manager) GetServerFingerprint() []byte {
|
||||
der := it.ExportPublicKeyDER()
|
||||
fp := it.GetOpenSSLLikeFingerprint(der)
|
||||
return []byte(fp)
|
||||
}
|
||||
|
||||
func (it *Manager) GetOpenSSLLikeFingerprint(input []byte) string {
|
||||
output := md5.Sum(input)
|
||||
items := make([]string, 16)
|
||||
for i := range output {
|
||||
items[i] = strings.ToLower(hex.EncodeToString([]byte{output[i]}))
|
||||
}
|
||||
|
||||
return strings.Join(items, ":")
|
||||
}
|
||||
|
||||
func (it *Manager) Register() {
|
||||
fp := it.GetServerFingerprint()
|
||||
|
||||
log.Printf("%s\n", fp)
|
||||
|
||||
signed, err := rsa.SignPKCS1v15(rand.Reader, it.privateKey, 0, fp)
|
||||
Check(err, "Failed signing fingerprint, err: %s", err)
|
||||
|
||||
signedHash := sha256.Sum256(signed)
|
||||
|
||||
publicKey := string(it.ExportPublicKey())
|
||||
hexSignature := hex.EncodeToString(signedHash[:])
|
||||
|
||||
log.Printf("Registering with\n\nsignature: %s\npublic key:\n\n%s", hexSignature, publicKey)
|
||||
|
||||
resp, err := http.PostForm(it.Config.Zerooo.Endpoint+"/server/register", url.Values{
|
||||
"publicKey": []string{publicKey},
|
||||
"signature": []string{hexSignature},
|
||||
})
|
||||
|
||||
Check(err, "Failed to sent register post to endpoint (%s/server/register), err: %s", it.Config.Zerooo.Endpoint, err)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
Error("Endpoint responded with %d, may not be actual zerooo endpoint", resp.StatusCode)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
Check(err, "Failed to read response, err: %s", err)
|
||||
|
||||
strBody := string(body)
|
||||
items := strings.SplitN(strBody, "\n", 2)
|
||||
code, err := strconv.ParseInt(items[0], 10, 64)
|
||||
Check(err, "Failed reading response success code, err: %s", err)
|
||||
|
||||
if code != 0 {
|
||||
Error("Register failed, server responded with:\n\n%s", items[1])
|
||||
}
|
||||
|
||||
log.Printf("Register succesful, server responded with: %s", items[1])
|
||||
}
|
||||
|
||||
func (it *Manager) ExportPublicKeyDER() []byte {
|
||||
if (it.privateKey == nil) {
|
||||
Error("No private key is loaded, can't export public key")
|
||||
}
|
||||
|
||||
der, err := x509.MarshalPKIXPublicKey(&it.privateKey.PublicKey)
|
||||
Check(err, "Failed converting public key to DER, err: %s", err)
|
||||
return der
|
||||
}
|
||||
|
||||
func (it *Manager) ExportPublicKey() []byte {
|
||||
pemKey := &pem.Block{
|
||||
Type: "PUBLIC KEY",
|
||||
Bytes: it.ExportPublicKeyDER(),
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemKey)
|
||||
}
|
||||
|
||||
func (it *Manager) GenerateKey() {
|
||||
log.Printf("No key exists, generating new 4096 RSA key")
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
Check(err, "Couldn't generate key, err: %s", err)
|
||||
it.privateKey = priv
|
||||
|
||||
pemKey := &pem.Block{
|
||||
Type: "RSA PRIVATE KEY",
|
||||
Bytes: x509.MarshalPKCS1PrivateKey(priv),
|
||||
}
|
||||
|
||||
keyFile, err := os.OpenFile(it.GetKeyLocation(), os.O_EXCL|os.O_CREATE|os.O_RDWR, 0600)
|
||||
Check(err, "Failed creating file at %q for private key, err: %s", it.GetKeyLocation(), err)
|
||||
defer keyFile.Close()
|
||||
|
||||
err = pem.Encode(keyFile, pemKey)
|
||||
Check(err, "Failed encoding private key, err: %s", err)
|
||||
}
|
||||
|
||||
func (it *Manager) LoadKey() {
|
||||
log.Printf("Loading generated key")
|
||||
|
||||
keyFile, err := os.Open(it.GetKeyLocation())
|
||||
Check(err, "Failed opening private key at %q for reading, err: %s", it.GetKeyLocation(), err)
|
||||
defer keyFile.Close()
|
||||
|
||||
stat, _ := keyFile.Stat()
|
||||
var size int64 = stat.Size()
|
||||
pembytes := make([]byte, size)
|
||||
buffer := bufio.NewReader(keyFile)
|
||||
_, err = buffer.Read(pembytes)
|
||||
Check(err, "Failed to read private key at %q, err: %s", it.GetKeyLocation(), err)
|
||||
|
||||
data, _ := pem.Decode([]byte(pembytes))
|
||||
if data == nil {
|
||||
Error("No valid key found at %q", it.GetKeyLocation())
|
||||
}
|
||||
|
||||
importedKey, err := x509.ParsePKCS1PrivateKey(data.Bytes)
|
||||
Check(err, "Failed importing private key at %q, err: %s", it.GetKeyLocation(), err)
|
||||
it.privateKey = importedKey
|
||||
}
|
||||
|
||||
func (it *Manager) Daemon() {
|
||||
server := NewHttpServer(it)
|
||||
go server.Start()
|
||||
|
||||
it.openVPN = NewOpenVPN(it)
|
||||
|
||||
it.openVPN.Start()
|
||||
|
||||
ch := make(chan os.Signal)
|
||||
|
||||
signal.Notify(ch, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGSTOP, syscall.SIGINT)
|
||||
|
||||
for sig := range ch {
|
||||
log.Printf("Received signal %s, quitting.", sig)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (it *Manager) LoadCA() {
|
||||
log.Printf("Loading CA certificate")
|
||||
|
||||
keyFile, err := os.Open(it.GetCALocation())
|
||||
Check(err, "Failed opening CA certificate at %q for reading, err: %s", it.GetKeyLocation(), err)
|
||||
defer keyFile.Close()
|
||||
|
||||
stat, _ := keyFile.Stat()
|
||||
var size int64 = stat.Size()
|
||||
pemBytes := make([]byte, size)
|
||||
buffer := bufio.NewReader(keyFile)
|
||||
_, err = buffer.Read(pemBytes)
|
||||
Check(err, "Failed to read CA certificate at %q, err: %s", it.GetKeyLocation(), err)
|
||||
|
||||
data, _ := pem.Decode([]byte(pemBytes))
|
||||
if data == nil {
|
||||
Error("No valid CA certificate found at %q", it.GetKeyLocation())
|
||||
}
|
||||
|
||||
ca, err := x509.ParseCertificate(data.Bytes)
|
||||
Check(err, "Failed importing private key at %q, err: %s", it.GetKeyLocation(), err)
|
||||
it.CA = ca
|
||||
}
|
||||
|
||||
func (it *Manager) DownloadCA() {
|
||||
caUrl := it.Config.Zerooo.Endpoint + "/ca"
|
||||
download("CA", it.GetCALocation(), caUrl)
|
||||
}
|
||||
|
||||
func (it *Manager) CAPublicKey() (*rsa.PublicKey) {
|
||||
key, ok := it.CA.PublicKey.(*rsa.PublicKey)
|
||||
if !ok {
|
||||
Error("Can't read CA Certificate as public key")
|
||||
}
|
||||
|
||||
return key
|
||||
}
|
||||
|
||||
func (it *Manager) CreateCSR(hostname string) ([]byte, error) {
|
||||
tpl := x509.CertificateRequest{Subject: pkix.Name{CommonName: hostname}}
|
||||
csr, err := x509.CreateCertificateRequest(rand.Reader, &tpl, it.privateKey)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pemCSR := &pem.Block{
|
||||
Type: "CERTIFICATE REQUEST",
|
||||
Bytes: csr,
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(pemCSR), nil
|
||||
}
|
||||
|
||||
func (it *Manager) UpdateCertificate(s string) error {
|
||||
f, err := os.OpenFile(it.GetCertificateLocation(), os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(s)
|
||||
return err
|
||||
}
|
||||
|
||||
func download(name string, path string, url string) {
|
||||
resp, err := http.Get(url)
|
||||
Check(err, "Failed fetching %s at %q, err: %s", name, url, err)
|
||||
cert, err := ioutil.ReadAll(resp.Body)
|
||||
Check(err, "Failed fetching %s at %q, err: %s", name, url, err)
|
||||
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
Check(err, "Failed open %s for writing at %q, err: %s", name, path, err)
|
||||
defer f.Close()
|
||||
_, err = f.Write(cert)
|
||||
Check(err, "Failed writing %s at %q, err: %s", name, path, err)
|
||||
}
|
||||
|
||||
func (it *Manager) DownloadCRL() {
|
||||
crlUrl := it.Config.Zerooo.Endpoint + "/crl"
|
||||
download("CRL", it.GetCRLLocation(), crlUrl)
|
||||
}
|
||||
|
||||
func (it *Manager) DownloadDH() {
|
||||
dhUrl := it.Config.Zerooo.Endpoint + "/dh.pem"
|
||||
download("DH", it.GetDHLocation(), dhUrl)
|
||||
}
|
||||
|
||||
func (it *Manager) IsOpenVPNReady() bool {
|
||||
hasConfig := fileExists(it.GetOpenVPNConfigLocation())
|
||||
hasCert := fileExists(it.GetCertificateLocation())
|
||||
return hasConfig && hasCert
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
type OpenVPN struct {
|
||||
Config *ZeroooConfig
|
||||
Manager *Manager
|
||||
shouldStop bool
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
func NewOpenVPN(manager *Manager) *OpenVPN {
|
||||
return &OpenVPN{
|
||||
Config: &manager.Config.Zerooo,
|
||||
Manager: manager,
|
||||
}
|
||||
}
|
||||
|
||||
func (it *OpenVPN) Stop() {
|
||||
it.shouldStop = true
|
||||
if it.cmd != nil && it.cmd.Process != nil {
|
||||
it.cmd.Process.Kill()
|
||||
}
|
||||
}
|
||||
|
||||
func (it *OpenVPN) GetConfigLocation() string {
|
||||
return it.Config.Location + "/server.conf"
|
||||
}
|
||||
|
||||
func (it *OpenVPN) Restart() {
|
||||
if it.cmd != nil && it.cmd.Process != nil {
|
||||
it.cmd.Process.Kill()
|
||||
return
|
||||
}
|
||||
|
||||
it.Start()
|
||||
}
|
||||
|
||||
func (it *OpenVPN) Start() {
|
||||
if !it.Manager.IsOpenVPNReady() {
|
||||
log.Printf("OpenVPN not ready yet.")
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Starting OpenVPN")
|
||||
it.cmd = exec.Command("openvpn", "--config", it.GetConfigLocation())
|
||||
it.cmd.Start()
|
||||
|
||||
go func() {
|
||||
it.cmd.Wait()
|
||||
if it.shouldStop {
|
||||
log.Printf("OpenVPN has stopped (exit code: %d)", it.cmd.ProcessState.ExitCode())
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("OpenVPN has stopped (exit code: %d), restarting", it.cmd.ProcessState.ExitCode())
|
||||
it.Start()
|
||||
}()
|
||||
}
|
||||
|
||||
func (it *OpenVPN) UpdateConfig(s string) error {
|
||||
f, err := os.OpenFile(it.GetConfigLocation(), os.O_RDWR|os.O_CREATE, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = f.WriteString(s)
|
||||
return err
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
func Check(err error, msg string, v ...interface{}) {
|
||||
if err != nil {
|
||||
Error(msg, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func Error(msg string, v ...interface{}) {
|
||||
log.Fatalf(msg+"\n", v...)
|
||||
os.Exit(1)
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"git.cijber.net/zer.ooo/service"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var daemonCmd = &cobra.Command{
|
||||
Use: "daemon",
|
||||
Short: "Starts the zer.ooo service daemon",
|
||||
Long: "Will start running the zer.ooo service daemon, keeping OpenVPN up",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := service.LoadConfig(ConfigPath)
|
||||
service.Check(err, "Failed loading config, err: %s", err)
|
||||
mgmt := service.NewManager(*config)
|
||||
mgmt.Init()
|
||||
mgmt.Daemon()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"git.cijber.net/zer.ooo/service"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
var (
|
||||
endpoint string
|
||||
location string
|
||||
)
|
||||
|
||||
var initCmd = &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Initialises an zer.ooo server install",
|
||||
Long: "Init (zerooo init) will initialise a server",
|
||||
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
config, err := service.LoadConfig(ConfigPath)
|
||||
service.Check(err, "Failed reading config, err: %s", err)
|
||||
|
||||
if (cmd.Flag("endpoint").Changed) {
|
||||
config.Zerooo.Endpoint = endpoint
|
||||
}
|
||||
|
||||
if (cmd.Flag("location").Changed) {
|
||||
if location[0] != '/' {
|
||||
cwd, err := os.Getwd()
|
||||
service.Check(err, "Can't get current working directory, err: %s", err)
|
||||
location = filepath.Join(cwd, location)
|
||||
}
|
||||
|
||||
config.Zerooo.Location = location
|
||||
}
|
||||
|
||||
service.CreateConfig(ConfigPath, config)
|
||||
mgmt := service.NewManager(*config)
|
||||
mgmt.Init()
|
||||
mgmt.Register()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
f := initCmd.Flags()
|
||||
f.StringVar(&endpoint, "endpoint", "https://zer.ooo", "Endpoint to register this server")
|
||||
f.StringVar(&location, "location", "/etc/zerooo", "Location of OpenVPN installation")
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
Copyright © 2019 eater
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"os"
|
||||
)
|
||||
|
||||
var ConfigPath string
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "zerooo",
|
||||
Short: "",
|
||||
Long: `Zer.ooo service manager`,
|
||||
}
|
||||
|
||||
// Execute adds all child commands to the root command and sets flags appropriately.
|
||||
// This is called by main.main(). It only needs to happen once to the rootCmd.
|
||||
func Execute() {
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVar(&ConfigPath, "config", "/etc/zerooo/service.toml", "config file")
|
||||
rootCmd.AddCommand(
|
||||
initCmd,
|
||||
daemonCmd,
|
||||
)
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package main
|
||||
|
||||
import "git.cijber.net/zer.ooo/service/zerooo/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
}
|
Loading…
Reference in New Issue