\"") {
envCapture = true
} else if cleanLine != "" {
b.starter.ProcessOutput(cleanLine)
}
}
}
}
func (b *BashEngine) Run(block string) (err error) {
cmd := exec.Command(b.shell, "-v", "-c", block+shellEnvExport)
cmd.Env = b.starter.GetEnviron()
cmd.Dir = b.starter.GetCwd()
stdout, err := cmd.StdoutPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to create stdout pipe"),
}
return
}
stderr, err := cmd.StderrPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to create stderr pipe"),
}
return
}
err = cmd.Start()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to start command"),
}
return
}
env := []string{}
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
env = b.streamOut(stdout)
wg.Done()
}()
go func() {
b.streamErr(stderr)
wg.Done()
}()
err = cmd.Wait()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Exit error in "+b.shell),
}
return
}
wg.Wait()
for _, pairStr := range env {
pair := strings.SplitN(pairStr, "=", 2)
if len(pair) != 2 {
continue
}
key, val := pair[0], pair[1]
if key == "PWD" {
b.starter.UpdateCwd(val)
} else if !b.curEnvKeys.Contains(key) {
b.starter.UpdateEnv(key, val)
}
}
return
}
================================================
FILE: engine/constants.go
================================================
package engine
const (
QueueSize = 256
)
================================================
FILE: engine/engine.go
================================================
package engine
import (
"fmt"
"os"
"strings"
"sync"
"sync/atomic"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/tools/logger"
)
type Engine struct {
cwd string
env map[string]string
blocks []*Block
bash *BashEngine
python *PythonEngine
lock sync.Mutex
outputLock sync.Mutex
fault atomic.Value
queue chan []*Block
OnStatus func(status string)
}
func (e *Engine) UpdateEnv(key, val string) {
e.env[key] = val
}
func (e *Engine) UpdateCwd(cwd string) {
e.cwd = cwd
}
func (e *Engine) ProcessOutput(output string) {
e.outputLock.Lock()
fmt.Println(output)
e.outputLock.Unlock()
}
func (e *Engine) GetEnv() map[string]string {
return e.env
}
func (e *Engine) GetCwd() string {
return e.cwd
}
func (e *Engine) GetEnviron() (env []string) {
env = []string{}
for _, pair := range os.Environ() {
key := strings.SplitN(pair, "=", 2)[0]
if e.env[key] == "" {
env = append(env, pair)
}
}
for key, val := range e.env {
env = append(env, key+"="+val)
}
return
}
func (e *Engine) Init() (err error) {
e.queue = make(chan []*Block, QueueSize)
e.fault.Store(false)
e.cwd, err = os.Getwd()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "starter: Failed to get working dir"),
}
return
}
e.env = map[string]string{}
e.bash = &BashEngine{}
err = e.bash.Init(e)
if err != nil {
return
}
e.python = &PythonEngine{}
err = e.python.Init(e)
if err != nil {
return
}
defer func() {
err2 := e.python.Exit()
if err2 != nil {
panic(err2)
}
}()
return
}
func (e *Engine) StartRunner() {
go e.runner()
}
func (e *Engine) getBlocks() (blocks []*Block) {
blocks = <-e.queue
for {
select {
case req := <-e.queue:
blocks = req
default:
return blocks
}
}
}
func (e *Engine) UpdateSpec(data string) (err error) {
blocks, err := Parse(data)
if err != nil {
return
}
e.blocks = blocks
return
}
func (e *Engine) runner() {
for {
blocks := e.getBlocks()
if !e.fault.Load().(bool) {
e.OnStatus(types.ReloadingClean)
} else {
e.OnStatus(types.ReloadingFault)
}
_, err := e.Run(Reload, blocks)
if err != nil {
logger.WithFields(logger.Fields{
"error": err,
}).Error("agent: Failed to run spec")
e.OnStatus(types.Fault)
} else {
e.OnStatus(types.Running)
}
}
}
func (e *Engine) Run(phase string, blocks []*Block) (fatal bool, err error) {
for i, block := range blocks {
switch phase {
case Initial:
break
case Reboot:
if block.Phase != Reboot && block.Phase != Reload {
continue
}
break
case Reload:
if block.Phase != Reload {
continue
}
break
}
err = e.runBlock(block.Type, block.Code)
if err != nil {
for _, block := range blocks[i:] {
switch phase {
case Initial:
if block.Phase != Reload {
fatal = true
}
case Reboot:
if block.Phase != Reboot && block.Phase != Reload {
continue
}
if block.Phase != Reload {
fatal = true
}
case Reload:
if block.Phase != Reload {
continue
}
}
}
e.fault.Store(true)
return
}
}
e.fault.Store(false)
return
}
func (e *Engine) runBlock(blockType, block string) (err error) {
switch blockType {
case "shell":
err = e.bash.Run(block)
if err != nil {
return
}
case "python":
err = e.python.Run(block)
if err != nil {
return
}
}
return
}
func (e *Engine) Queue(data string) {
blocks, err := Parse(data)
if err != nil {
return
}
e.lock.Lock()
if len(e.queue) >= QueueSize-16 {
return
}
e.queue <- blocks
e.lock.Unlock()
}
================================================
FILE: engine/parser.go
================================================
package engine
import (
"regexp"
"strings"
)
type Block struct {
Type string
Phase string
Code string
LineNum int
}
const (
Initial = "initial"
Reboot = "reboot"
Reload = "reload"
Image = "image"
)
var (
codeBlockRe = regexp.MustCompile(`^([a-zA-Z]+)\s*(\{([^}]+)\})?$`)
)
func Parse(data string) (blocks []*Block, err error) {
blocks = []*Block{}
var curBlock *Block
for n, line := range strings.Split(data, "\n") {
if curBlock == nil {
if strings.HasPrefix(line, "```") {
lang, attrs := parseCodeBlockHeader(line[3:])
phase := Initial
if attrs != nil {
switch attrs["phase"] {
case Initial:
phase = Initial
case Reboot:
phase = Reboot
case Reload:
phase = Reload
}
}
switch lang {
case "shell":
curBlock = &Block{
Type: "shell",
Phase: phase,
}
case "python":
curBlock = &Block{
Type: "python",
Phase: phase,
}
}
}
} else {
if line == "```" {
curBlock.LineNum = n + 1
blocks = append(blocks, curBlock)
curBlock = nil
} else {
curBlock.Code += line + "\n"
}
}
}
return
}
func parseCodeBlockHeader(input string) (language string,
attrs map[string]string) {
attrs = map[string]string{}
matches := codeBlockRe.FindStringSubmatch(input)
if len(matches) == 0 {
return
}
language = matches[1]
if len(matches) < 3 {
return
}
attrPairs := strings.Split(matches[2], ",")
for _, pair := range attrPairs {
pair = strings.TrimPrefix(pair, "{")
pair = strings.TrimSuffix(pair, "}")
keyValue := strings.SplitN(pair, "=", 2)
if len(keyValue) == 2 {
key := strings.TrimSpace(keyValue[0])
value := strings.Trim(strings.TrimSpace(keyValue[1]), `"`)
attrs[key] = value
}
}
return
}
================================================
FILE: engine/python.go
================================================
package engine
import (
"bufio"
"container/list"
"encoding/json"
"fmt"
"io"
"os/exec"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var (
PythonExec = "python3"
)
const pyEngine = `#!/usr/bin/env python3
import platform
import os
import json
import traceback
def _pystarter_get_startup():
python_version = platform.python_version()
compiler = platform.python_compiler()
os_name = platform.system().lower() + " " + platform.release()
build_date = " ".join(platform.python_build()[1].split()[1:4])
return f"Python {python_version} ({build_date}) [{compiler}] on {os_name}"
def export(name, arg):
name = ''.join(c for c in name if c.isalnum() or c == '_')
arg = json.dumps(arg)
print(f"{name}={arg}")
print(_pystarter_get_startup())
print("")
while True:
data = input()
if not data:
continue
data = json.loads(data)
if data["type"] == "exit":
exit(0)
elif data["type"] == "env":
for key, val in data["env"].items():
os.environ[key] = val
print("")
elif data["type"] == "chdir":
os.chdir(data["input"])
print("")
elif data["type"] == "exec":
try:
exec(data["input"])
except Exception as err:
print(f"An error occurred: {err}")
print(f"Exception type: {type(err).__name__}")
traceback.print_exc()
print(f""
f"{os.getcwd()}")
`
type pythonData struct {
Type string `json:"type"`
Input string `json:"input"`
Env map[string]string `json:"env"`
}
type PythonEngine struct {
cmd *exec.Cmd
cwd string
stdin io.WriteCloser
stdout io.ReadCloser
stderr io.ReadCloser
cmdErr error
inputLines *list.List
waiter chan bool
initialized bool
starter *Engine
output string
}
func (p *PythonEngine) Init(strt *Engine) (err error) {
p.starter = strt
return
}
func (p *PythonEngine) start() (err error) {
p.waiter = make(chan bool, 16)
p.inputLines = list.New()
p.cmd = exec.Command(PythonExec, "-u", "-c", pyEngine)
p.cmd.Env = p.starter.GetEnviron()
p.cmd.Dir = p.starter.GetCwd()
p.stdout, err = p.cmd.StdoutPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to get py stdout"),
}
return
}
p.stderr, err = p.cmd.StderrPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to get py stderr"),
}
return
}
p.stdin, err = p.cmd.StdinPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to get py stdin"),
}
return
}
err = p.cmd.Start()
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to start py"),
}
return
}
p.wait()
p.copyOutput(p.stdout)
p.copyOutput(p.stderr)
<-p.waiter
return
}
func (p *PythonEngine) flushOutput() {
if p.output == "" {
return
}
output := p.output
p.output = ""
output = strings.TrimRight(output, "\n")
p.starter.ProcessOutput(output)
}
func (p *PythonEngine) copyOutput(src io.ReadCloser) {
go func() {
scanner := bufio.NewScanner(src)
for scanner.Scan() {
output := scanner.Text()
if !p.initialized {
if strings.Contains(output, "") {
p.initialized = true
p.waiter <- true
} else {
p.starter.ProcessOutput(output)
}
continue
}
execDoneStart := strings.Index(output, "")
exportStart := strings.Index(output, "")
if execDoneStart != -1 {
execDoneStart += 25
execDoneEnd := strings.Index(output,
"")
if execDoneEnd == -1 {
err := &errortypes.ExecError{
errors.Newf(
"starter: Incomplete exec response '%s'", output),
}
panic(err)
}
p.starter.UpdateCwd(output[execDoneStart:execDoneEnd])
p.waiter <- true
} else if exportStart != -1 {
exportStart += 22
exportEnd := strings.Index(output, "")
if exportEnd == -1 {
err := &errortypes.ExecError{
errors.Newf(
"starter: Incomplete export '%s'", output),
}
panic(err)
}
pair := strings.SplitN(output[exportStart:exportEnd], "=", 2)
if len(pair) == 2 {
key, val := pair[0], pair[1]
if val[0] == '"' && val[len(val)-1] == '"' {
val = val[1 : len(val)-1]
}
p.starter.UpdateEnv(key, val)
}
} else if strings.Contains(output,
"") {
p.waiter <- true
} else {
p.output += output + "\n"
}
}
}()
}
func (p *PythonEngine) wait() {
go func() {
defer func() {
if p.stdin != nil {
_ = p.stdin.Close()
}
}()
err := p.cmd.Wait()
if err != nil {
p.flushOutput()
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Exit error in py"),
}
p.cmdErr = err
p.waiter <- true
return
}
p.waiter <- true
}()
}
func (p *PythonEngine) updateEnv() (err error) {
defer func() {
p.flushOutput()
}()
p.waiter = make(chan bool, 16)
data := &pythonData{
Type: "env",
Env: p.starter.GetEnv(),
}
dataIn, err := json.Marshal(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "starter: Failed to marshal env"),
}
return
}
_, err = fmt.Fprintln(p.stdin, string(dataIn))
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to update env in py"),
}
return
}
<-p.waiter
return
}
func (p *PythonEngine) updateCwd() (err error) {
defer func() {
p.flushOutput()
}()
p.waiter = make(chan bool, 16)
data := &pythonData{
Type: "chdir",
Input: p.starter.GetCwd(),
}
dataIn, err := json.Marshal(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "starter: Failed to marshal env"),
}
return
}
_, err = fmt.Fprintln(p.stdin, string(dataIn))
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to update env in py"),
}
return
}
<-p.waiter
return
}
func (p *PythonEngine) run(code string) (err error) {
defer func() {
p.flushOutput()
}()
p.waiter = make(chan bool, 16)
data := &pythonData{
Type: "exec",
Input: code,
}
dataIn, err := json.Marshal(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "starter: Failed to marshal code"),
}
return
}
_, err = fmt.Fprintln(p.stdin, string(dataIn))
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to run code in py"),
}
return
}
<-p.waiter
return
}
func (p *PythonEngine) Exit() (err error) {
p.waiter = make(chan bool, 16)
if p.cmd == nil {
return
}
data := &pythonData{
Type: "exit",
}
dataIn, err := json.Marshal(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "starter: Failed to marshal exit signal"),
}
return
}
_, err = fmt.Fprintln(p.stdin, string(dataIn))
if err != nil {
err = &errortypes.ExecError{
errors.Wrap(err, "starter: Failed to run exit signal in py"),
}
return
}
<-p.waiter
if p.cmdErr != nil {
err = p.cmdErr
}
return
}
func (p *PythonEngine) Run(code string) (err error) {
if p.cmd == nil {
err = p.start()
if err != nil {
return
}
}
err = p.updateEnv()
if err != nil {
return
}
err = p.updateCwd()
if err != nil {
return
}
for _, line := range strings.Split(code, "\n") {
p.starter.ProcessOutput(">>> " + line)
}
err = p.run(code)
if err != nil {
return
}
if p.cmdErr != nil {
err = p.cmdErr
return
}
return
}
================================================
FILE: errortypes/errortypes.go
================================================
package errortypes
import (
"github.com/dropbox/godropbox/errors"
)
type UnknownError struct {
errors.DropboxError
}
type NotFoundError struct {
errors.DropboxError
}
type ReadError struct {
errors.DropboxError
}
type WriteError struct {
errors.DropboxError
}
type ParseError struct {
errors.DropboxError
}
type AuthenticationError struct {
errors.DropboxError
}
type VerificationError struct {
errors.DropboxError
}
type ApiError struct {
errors.DropboxError
}
type DatabaseError struct {
errors.DropboxError
}
type RequestError struct {
errors.DropboxError
}
type ConnectionError struct {
errors.DropboxError
}
type TimeoutError struct {
errors.DropboxError
}
type ExecError struct {
errors.DropboxError
}
type NetworkError struct {
errors.DropboxError
}
type TypeError struct {
errors.DropboxError
}
type ErrorData struct {
Error string `json:"error"`
Message string `json:"error_msg"`
}
func (e *ErrorData) GetError() (err error) {
err = &ParseError{
errors.Newf("error: Parse error %s - %s", e.Error, e.Message),
}
return
}
func GetErrorMessage(err error) string {
if err == nil {
return ""
}
if intErr, ok := err.(errors.DropboxError); ok {
return intErr.GetMessage()
}
return err.Error()
}
================================================
FILE: eval/constants.go
================================================
package eval
import (
"github.com/dropbox/godropbox/container/set"
)
type Equal struct{}
type NotEqual struct{}
type Less struct{}
type LessEqual struct{}
type Greater struct{}
type GreaterEqual struct{}
type If struct{}
type And struct{}
type Or struct{}
type For struct{}
type Then struct{}
const (
StatementMaxLength = 1024
StatementMaxParts = 30
)
var StatementSafeCharacters = set.NewSet(
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
' ',
'.',
'_',
'-',
'=',
'>',
'<',
'!',
'\'',
'(',
')',
)
================================================
FILE: eval/errortypes.go
================================================
package eval
import (
"bytes"
"fmt"
"html/template"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
)
type EvalError struct {
Statement string
Index int
ErrIndex int
Length int
errors.DropboxError
}
func NewEvalError(statement string, index, errorIndex int,
length int, templMsg string, args ...interface{}) (err error) {
evalErr := &EvalError{
Statement: statement,
Index: index + 1,
ErrIndex: errorIndex + 1,
Length: length,
}
tmpl, err := template.New("eval").Parse(templMsg)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "eval: Failed to parse eval error template"),
}
return
}
errorMsg := &bytes.Buffer{}
err = tmpl.Execute(errorMsg, evalErr)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "eval: Failed to execute eval error template"),
}
return
}
errorMsgStr := fmt.Sprintf(errorMsg.String(), args...)
errorMsgStr += fmt.Sprintf(" index=%d", evalErr.Index)
errorMsgStr += fmt.Sprintf(" error_index=%d", evalErr.ErrIndex)
errorMsgStr += fmt.Sprintf(" statement=\"%s\"", evalErr.Statement)
evalErr.DropboxError = errors.New(errorMsgStr)
err = evalErr
return
}
================================================
FILE: eval/eval.go
================================================
package eval
import (
"reflect"
"strconv"
"strings"
)
type Data map[string]map[string]interface{}
type Parser struct {
statement string
parts []string
partsLen int
data Data
}
func (p *Parser) parseRef(ref string, pos int) (val interface{}, err error) {
n := len(ref)
if n == 0 {
return
}
if ref[0] == '\'' {
if ref[n-1] != '\'' {
err = NewEvalError(
p.statement,
pos,
pos,
p.partsLen,
"eval: Invalid string {{.ErrIndex}}",
)
return
}
val = ref[1 : n-1]
return
}
switch ref {
case "true":
val = true
return
case "false":
val = true
return
case "==":
val = Equal{}
return
case "!=":
val = NotEqual{}
return
case "<":
val = Less{}
return
case "<=":
val = LessEqual{}
return
case ">":
val = Greater{}
return
case ">=":
val = GreaterEqual{}
return
case "IF":
val = If{}
return
case "AND":
val = And{}
return
case "OR":
val = Or{}
return
case "FOR":
val = For{}
return
case "THEN":
val = Then{}
return
}
if ref == "true" {
val = true
return
} else if ref == "false" {
val = false
return
}
intVal, e := strconv.Atoi(ref)
if e == nil {
val = intVal
return
}
floatVal, e := strconv.ParseFloat(ref, 64)
if e == nil {
val = floatVal
return
}
split := strings.Split(ref, ".")
if len(split) != 2 {
err = NewEvalError(
p.statement,
pos,
pos,
p.partsLen,
"eval: Invalid reference {{.ErrIndex}}",
)
return
}
group := p.data[split[0]]
if group == nil {
err = NewEvalError(
p.statement,
pos,
pos,
p.partsLen,
"eval: Invalid reference group {{.ErrIndex}}",
)
return
}
groupVal := group[split[1]]
if groupVal == nil {
err = NewEvalError(
p.statement,
pos,
pos,
p.partsLen,
"eval: Invalid reference group key {{.ErrIndex}}",
)
return
} else {
if fVal, ok := groupVal.(float64); ok {
if fVal == float64(int(fVal)) {
val = int(fVal)
} else {
val = fVal
}
} else {
val = groupVal
}
}
return
}
func (p *Parser) parseComp(left, right, comp interface{}) bool {
if reflect.TypeOf(left) != reflect.TypeOf(right) {
return false
}
switch leftVal := left.(type) {
case bool:
switch comp.(type) {
case Equal:
return leftVal == right.(bool)
case NotEqual:
return leftVal != right.(bool)
case Less:
return false
case LessEqual:
return false
case Greater:
return false
case GreaterEqual:
return false
default:
panic("Invalid comp")
}
case string:
switch comp.(type) {
case Equal:
return leftVal == right.(string)
case NotEqual:
return leftVal != right.(string)
case Less:
return leftVal < right.(string)
case LessEqual:
return leftVal <= right.(string)
case Greater:
return leftVal > right.(string)
case GreaterEqual:
return leftVal >= right.(string)
default:
panic("Invalid comp")
}
case int:
switch comp.(type) {
case Equal:
return leftVal == right.(int)
case NotEqual:
return leftVal != right.(int)
case Less:
return leftVal < right.(int)
case LessEqual:
return leftVal <= right.(int)
case Greater:
return leftVal > right.(int)
case GreaterEqual:
return leftVal >= right.(int)
default:
panic("Invalid comp")
}
case float64:
switch comp.(type) {
case Equal:
return leftVal == right.(float64)
case NotEqual:
return leftVal != right.(float64)
case Less:
return leftVal < right.(float64)
case LessEqual:
return leftVal <= right.(float64)
case Greater:
return leftVal > right.(float64)
case GreaterEqual:
return leftVal >= right.(float64)
default:
panic("Invalid comp")
}
default:
panic("Invalid type")
}
return false
}
func (p *Parser) Eval() (resp string, threshold int, err error) {
p.parts = strings.Fields(p.statement)
p.partsLen = len(p.parts)
if p.partsLen < 6 {
err = NewEvalError(
p.statement,
0,
0,
p.partsLen,
"eval: Statement under min parts",
)
return
} else if p.partsLen > 30 {
err = NewEvalError(
p.statement,
0,
0,
p.partsLen,
"eval: Statement exceeds max parts",
)
return
} else if len(p.statement) > 1024 {
err = NewEvalError(
p.statement,
0,
0,
p.partsLen,
"eval: Statement exceeds max length",
)
return
}
if p.parts[0] != "IF" {
err = NewEvalError(
p.statement,
0,
0,
p.partsLen,
"eval: Statement part {{.ErrorIndex}} invalid",
)
return
}
i := 1
var expr interface{}
results := []bool{}
final := false
for x := 0; x < 100; x++ {
if p.partsLen < i+4 {
err = NewEvalError(
p.statement,
i,
i,
p.partsLen,
"eval: Incomplete expression",
)
return
}
index := i
i += 4
leftOp, e := p.parseRef(p.parts[index], index)
if e != nil {
err = e
return
}
comp, e := p.parseRef(p.parts[index+1], index+1)
if e != nil {
err = e
return
}
rightOp, e := p.parseRef(p.parts[index+2], index+2)
if e != nil {
err = e
return
}
next, e := p.parseRef(p.parts[index+3], index+3)
if e != nil {
err = e
return
}
switch leftOp.(type) {
case string:
break
case int:
break
case float64:
break
case bool:
break
default:
err = NewEvalError(
p.statement,
index,
index,
p.partsLen,
"eval: Invalid left operator {{.ErrIndex}}",
)
return
}
switch rightOp.(type) {
case string:
break
case int:
break
case float64:
break
case bool:
break
default:
err = NewEvalError(
p.statement,
index,
index+2,
p.partsLen,
"eval: Invalid right operator {{.ErrIndex}}",
)
return
}
switch comp.(type) {
case Equal:
break
case NotEqual:
break
case Less:
break
case LessEqual:
break
case Greater:
break
case GreaterEqual:
break
default:
err = NewEvalError(
p.statement,
index,
index+1,
p.partsLen,
"eval: Invalid comparison operator {{.ErrIndex}}",
)
return
}
result := p.parseComp(leftOp, rightOp, comp)
results = append(results, result)
if _, ok := next.(For); ok {
if index+6 != p.partsLen-1 {
err = NewEvalError(
p.statement,
index,
index+6,
p.partsLen,
"eval: Expected %d length", index+6,
)
return
}
next, e = p.parseRef(p.parts[index+5], index+5)
if e != nil {
err = e
return
}
if _, ok := next.(Then); !ok {
err = NewEvalError(
p.statement,
index,
index+5,
p.partsLen,
"eval: Expected THAN at {{.ErrIndex}}",
)
return
}
forVal, e := p.parseRef(p.parts[index+4], index+4)
if e != nil {
err = e
return
}
if forInt, ok := forVal.(int); ok {
threshold = forInt
} else {
err = NewEvalError(
p.statement,
index,
index+4,
p.partsLen,
"eval: Expected FOR value to be int",
)
return
}
index += 2
}
switch next.(type) {
case And:
if expr == nil {
expr = And{}
} else if _, ok := expr.(And); !ok {
err = NewEvalError(
p.statement,
index,
index+2,
p.partsLen,
"eval: Cannot mix OR with AND",
)
return
}
break
case Or:
if expr == nil {
expr = Or{}
} else if _, ok := expr.(Or); !ok {
err = NewEvalError(
p.statement,
index,
index+2,
p.partsLen,
"eval: Cannot mix OR with AND",
)
return
}
break
case Then:
if index+4 != p.partsLen-1 {
err = NewEvalError(
p.statement,
index,
index,
p.partsLen,
"eval: Expected %d length", index+4,
)
return
}
if _, ok := expr.(Or); ok {
for _, result := range results {
if result {
final = true
break
}
}
} else {
if len(results) == 0 {
final = false
} else {
final = true
for _, result := range results {
if !result {
final = false
break
}
}
}
}
if final {
respInf, e := p.parseRef(p.parts[index+4], index+4)
if e != nil {
err = e
return
}
if respStr, ok := respInf.(string); ok {
resp = respStr
} else {
err = NewEvalError(
p.statement,
index,
index+4,
p.partsLen,
"eval: Result must be string",
)
return
}
}
return
default:
err = NewEvalError(
p.statement,
index,
index+3,
p.partsLen,
"eval: Invalid continuation",
)
return
}
}
err = NewEvalError(
p.statement,
0,
0,
p.partsLen,
"eval: Infinite loop",
)
return
}
func Eval(data Data, statement string) (resp string,
threshold int, err error) {
parsr := &Parser{
statement: statement,
data: data,
}
resp, threshold, err = parsr.Eval()
if err != nil {
return
}
return
}
================================================
FILE: eval/utils.go
================================================
package eval
import (
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
)
func Validate(statement string) (err error) {
if len(statement) == 0 {
err = &errortypes.ParseError{
errors.New("eval: Empty statement"),
}
return
}
if len(statement) > StatementMaxLength {
err = &errortypes.ParseError{
errors.Newf("eval: Statement exceeds max length"),
}
return
}
for i, c := range statement {
if !StatementSafeCharacters.Contains(c) {
err = &errortypes.ParseError{
errors.Newf("eval: Illegal char (%s) at %d", string(c), i+1),
}
return
}
}
return
}
================================================
FILE: event/event.go
================================================
package event
import (
"fmt"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/sirupsen/logrus"
)
var (
listeners = map[string][]func(*EventPublish){}
)
type Event struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Channel string `bson:"channel" json:"channel"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Data bson.M `bson:"data" json:"data"`
}
type EventPublish struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Channel string `bson:"channel" json:"channel"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Data interface{} `bson:"data" json:"data"`
}
type CustomEvent interface {
GetId() bson.ObjectID
GetData() interface{}
}
type Dispatch struct {
Type string `bson:"type" json:"type"`
}
func getCursorId(db *database.Database, coll *database.Collection,
channels []string) (id bson.ObjectID, err error) {
msg := &EventPublish{}
var query *bson.M
if len(channels) == 1 {
query = &bson.M{
"channel": channels[0],
}
} else {
query = &bson.M{
"channel": &bson.M{
"$in": channels,
},
}
}
for i := 0; i < 2; i++ {
err = coll.FindOne(
db,
query,
options.FindOne().
SetSort(bson.D{{"$natural", -1}}),
).Decode(msg)
if err != nil {
err = database.ParseError(err)
if i > 0 {
return
}
switch err.(type) {
case *database.NotFoundError:
// Cannot use client-side ObjectId for tailable collection
err = Publish(db, channels[0], nil)
if err != nil {
err = database.ParseError(err)
return
}
continue
default:
return
}
} else {
break
}
}
id = msg.Id
return
}
func getCursorIdRetry(channels []string) bson.ObjectID {
db := database.GetDatabase()
defer db.Close()
for {
coll := db.Events()
cursorId, err := getCursorId(db, coll, channels)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Subscribe cursor error")
db.Close()
db = database.GetDatabase()
time.Sleep(constants.RetryDelay)
continue
}
return cursorId
}
}
func Publish(db *database.Database, channel string, data interface{}) (
err error) {
coll := db.Events()
msg := &EventPublish{
Id: bson.NewObjectID(),
Channel: channel,
Timestamp: time.Now(),
Data: data,
}
_, err = coll.InsertOne(db, msg)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func PublishDispatch(db *database.Database, typ string) (
err error) {
evt := &Dispatch{
Type: typ,
}
err = Publish(db, "dispatch", evt)
if err != nil {
return
}
return
}
func Subscribe(channels []string, duration time.Duration,
onMsg func(*EventPublish, error) bool) {
db := database.GetDatabase()
defer db.Close()
coll := db.Events()
cursorId := getCursorIdRetry(channels)
var channelBson interface{}
if len(channels) == 1 {
channelBson = channels[0]
} else {
channelBson = &bson.M{
"$in": channels,
}
}
queryOpts := options.Find().
SetSort(bson.D{{"$natural", 1}}).
SetMaxAwaitTime(duration).
SetCursorType(options.TailableAwait)
query := bson.M{
"_id": bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
var cursor *mongo.Cursor
var err error
for {
cursor, err = coll.Find(
db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
if !onMsg(nil, err) {
return
}
} else {
break
}
time.Sleep(constants.RetryDelay)
}
defer func() {
defer func() {
recover()
}()
if r := recover(); r != nil {
logrus.WithFields(logrus.Fields{
"error": errors.New(fmt.Sprintf("%s", r)),
}).Error("event: Event panic")
}
cursor.Close(db)
}()
for {
for cursor.Next(db) {
msg := &EventPublish{}
err = cursor.Decode(msg)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener decode error")
if !onMsg(nil, err) {
return
}
time.Sleep(constants.RetryDelay)
break
}
cursorId = msg.Id
if msg.Data == nil {
// Blank msg for cursor
continue
}
if !onMsg(msg, nil) {
return
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener cursor error")
if !onMsg(nil, err) {
return
}
time.Sleep(constants.RetryDelay)
}
cursor.Close(db)
db.Close()
db = database.GetDatabase()
coll = db.Events()
query := &bson.M{
"_id": &bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
for {
cursor, err = coll.Find(
db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
if !onMsg(nil, err) {
return
}
} else {
break
}
time.Sleep(constants.RetryDelay)
}
}
}
func SubscribeType(channels []string, duration time.Duration,
newEvent func() CustomEvent, onMsg func(CustomEvent, error) bool) {
db := database.GetDatabase()
defer db.Close()
coll := db.Events()
cursorId := getCursorIdRetry(channels)
var channelBson interface{}
if len(channels) == 1 {
channelBson = channels[0]
} else {
channelBson = &bson.M{
"$in": channels,
}
}
queryOpts := options.Find().
SetSort(bson.D{{"$natural", 1}}).
SetMaxAwaitTime(duration).
SetCursorType(options.TailableAwait)
query := &bson.M{
"_id": &bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
var cursor *mongo.Cursor
var err error
for {
cursor, err = coll.Find(
db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
if !onMsg(nil, err) {
return
}
} else {
break
}
time.Sleep(constants.RetryDelay)
}
defer func() {
defer func() {
recover()
}()
cursor.Close(db)
}()
for {
for cursor.Next(db) {
msg := newEvent()
err = cursor.Decode(msg)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener decode error")
if !onMsg(nil, err) {
return
}
time.Sleep(constants.RetryDelay)
break
}
cursorId = msg.GetId()
if msg.GetData() == nil {
// Blank msg for cursor
continue
}
if !onMsg(msg, nil) {
return
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener cursor error")
if !onMsg(nil, err) {
return
}
time.Sleep(constants.RetryDelay)
}
cursor.Close(db)
db.Close()
db = database.GetDatabase()
coll = db.Events()
query := &bson.M{
"_id": &bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
for {
cursor, err = coll.Find(
db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
if !onMsg(nil, err) {
return
}
} else {
break
}
time.Sleep(constants.RetryDelay)
}
}
}
func Register(channel string, callback func(*EventPublish)) {
callbacks := listeners[channel]
if callbacks == nil {
callbacks = []func(*EventPublish){}
}
listeners[channel] = append(callbacks, callback)
}
func subscribe(channels []string) {
Subscribe(channels, 10*time.Second,
func(msg *EventPublish, err error) bool {
if msg == nil || err != nil {
return true
}
for _, listener := range listeners[msg.Channel] {
listener(msg)
}
return true
})
}
func init() {
module := requires.New("event")
module.After("settings")
module.Handler = func() (err error) {
go func() {
channels := []string{}
for channel := range listeners {
channels = append(channels, channel)
}
if len(channels) > 0 {
subscribe(channels)
}
}()
return
}
}
================================================
FILE: event/listener.go
================================================
package event
import (
"fmt"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/sirupsen/logrus"
)
type Listener struct {
db *database.Database
state bool
channels []string
stream chan *Event
once sync.Once
}
func (l *Listener) Listen() chan *Event {
return l.stream
}
func (l *Listener) Close() {
l.state = false
l.once.Do(func() {
close(l.stream)
})
}
func (l *Listener) sub(cursorId bson.ObjectID) {
coll := l.db.Events()
var channelBson interface{}
if len(l.channels) == 1 {
channelBson = l.channels[0]
} else {
channelBson = &bson.M{
"$in": l.channels,
}
}
queryOpts := options.Find().
SetSort(bson.D{{"$natural", 1}}).
SetMaxAwaitTime(10 * time.Second).
SetCursorType(options.TailableAwait)
query := &bson.M{
"_id": &bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
var cursor *mongo.Cursor
var err error
for {
cursor, err = coll.Find(
l.db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
} else {
break
}
if !l.state {
return
}
time.Sleep(constants.RetryDelay)
if !l.state {
return
}
}
defer func() {
defer func() {
recover()
}()
if r := recover(); r != nil {
logrus.WithFields(logrus.Fields{
"error": errors.New(fmt.Sprintf("%s", r)),
}).Error("event: Event panic")
}
cursor.Close(l.db)
}()
for {
for cursor.Next(l.db) {
msg := &Event{}
err = cursor.Decode(msg)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener decode error")
time.Sleep(constants.RetryDelay)
break
}
cursorId = msg.Id
if msg.Data == nil {
// Blank msg for cursor
continue
}
if !l.state {
return
}
l.stream <- msg
}
if !l.state {
return
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener cursor error")
time.Sleep(constants.RetryDelay)
}
if !l.state {
return
}
cursor.Close(l.db)
coll = l.db.Events()
query := &bson.M{
"_id": &bson.M{
"$gt": cursorId,
},
"channel": channelBson,
}
for {
cursor, err = coll.Find(
l.db,
query,
queryOpts,
)
if err != nil {
err = database.ParseError(err)
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("event: Listener find error")
} else {
break
}
if !l.state {
return
}
time.Sleep(constants.RetryDelay)
if !l.state {
return
}
}
}
}
func (l *Listener) init() (err error) {
coll := l.db.Events()
cursorId, err := getCursorId(l.db, coll, l.channels)
if err != nil {
err = database.ParseError(err)
return
}
l.state = true
go func() {
if r := recover(); r != nil {
logrus.WithFields(logrus.Fields{
"error": errors.New(fmt.Sprintf("%s", r)),
}).Error("event: Listener panic")
}
l.sub(cursorId)
}()
return
}
func SubscribeListener(db *database.Database, channels []string) (
lst *Listener, err error) {
lst = &Listener{
db: db,
channels: channels,
stream: make(chan *Event, 10),
}
err = lst.init()
if err != nil {
return
}
return
}
================================================
FILE: event/socket.go
================================================
package event
import (
"context"
"net/http"
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/gorilla/websocket"
)
var (
Upgrader = websocket.Upgrader{
HandshakeTimeout: 30 * time.Second,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
WebSockets = set.NewSet()
WebSocketsLock = sync.Mutex{}
)
type WebSocket struct {
Conn *websocket.Conn
Ticker *time.Ticker
Listener *Listener
Cancel context.CancelFunc
Closed bool
}
func (w *WebSocket) Close() {
w.Closed = true
func() {
defer func() {
recover()
}()
w.Cancel()
}()
func() {
defer func() {
recover()
}()
w.Ticker.Stop()
}()
func() {
defer func() {
recover()
}()
w.Listener.Close()
}()
func() {
defer func() {
recover()
}()
w.Conn.Close()
}()
}
func WebSocketsStop() {
WebSocketsLock.Lock()
for socketInf := range WebSockets.Iter() {
func() {
socket := socketInf.(*WebSocket)
socket.Close()
}()
}
WebSockets = set.NewSet()
WebSocketsLock.Unlock()
}
================================================
FILE: features/qemu.go
================================================
package features
import (
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
verCache = version{}
verCacheLock = sync.Mutex{}
verCacheTime = time.Time{}
)
const (
Libexec = "/usr/libexec/qemu-kvm"
System = "/usr/bin/qemu-system-x86_64"
)
type version struct {
Major int
Minor int
Patch int
}
func GetQemuPath() (path string, err error) {
exists, err := utils.Exists(System)
if err != nil {
return
}
if exists {
path = System
} else {
path = Libexec
}
return
}
func GetQemuVersion() (major, minor, patch int, err error) {
verCacheLock.Lock()
if time.Since(verCacheTime) < 1*time.Minute {
major = verCache.Major
minor = verCache.Minor
patch = verCache.Patch
verCacheLock.Unlock()
return
}
verCacheLock.Unlock()
qemuPath, err := GetQemuPath()
if err != nil {
return
}
output, _ := utils.ExecCombinedOutputLogged(
nil,
qemuPath, "--version",
)
lines := strings.Split(output, "\n")
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) < 4 || strings.ToLower(fields[2]) != "version" {
continue
}
versions := strings.Split(fields[3], ".")
if len(versions) != 3 {
continue
}
var e error
major, e = strconv.Atoi(versions[0])
if e != nil {
continue
}
minor, e = strconv.Atoi(versions[1])
if e != nil {
major = 0
continue
}
patch, e = strconv.Atoi(versions[2])
if e != nil {
major = 0
minor = 0
continue
}
break
}
if major == 0 {
err = &errortypes.ParseError{
errors.Newf("qemu: Invalid Qemu version '%s'", output),
}
return
}
verCacheLock.Lock()
verCache.Major = major
verCache.Minor = minor
verCache.Patch = patch
verCacheTime = time.Now()
verCacheLock.Unlock()
return
}
func GetKernelVersion() (major, minor, patch int, err error) {
uname := &syscall.Utsname{}
err = syscall.Uname(uname)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qemu: Failed to get syscall uname"),
}
return
}
version := utils.Int8Str(uname.Release[:])
versions := strings.Split(version, "-")
if len(versions) < 2 {
err = &errortypes.ParseError{
errors.Newf(
"qemu: Failed to parse uname version 1 '%s'",
version,
),
}
return
}
versions = strings.Split(versions[0], ".")
if len(versions) < 3 {
err = &errortypes.ParseError{
errors.Newf(
"qemu: Failed to parse uname version 2 '%s'",
version,
),
}
return
}
major, err = strconv.Atoi(versions[0])
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qemu: Failed to parse uname version 3 '%s'",
version,
),
}
return
}
minor, err = strconv.Atoi(versions[1])
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qemu: Failed to parse uname version 4 '%s'",
version,
),
}
return
}
patch, err = strconv.Atoi(versions[2])
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qemu: Failed to parse uname version 5 '%s'",
version,
),
}
return
}
return
}
func GetUringSupport() (supported bool, err error) {
kallsyms, err := ioutil.ReadFile("/proc/kallsyms")
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "features: Failed to read /proc/kallsyms"),
}
return
}
if !strings.Contains(string(kallsyms), "io_uring_init") {
return
}
major, minor, _, err := GetKernelVersion()
if err != nil {
return
}
if major < 5 {
return
} else if major == 5 && minor < 2 {
return
}
major, minor, _, err = GetQemuVersion()
if err != nil {
return
}
if major < 6 {
return
} else if major == 6 && minor < 2 {
return
}
sysctlData, err := ioutil.ReadFile("/proc/sys/kernel/io_uring_disabled")
if err != nil {
if os.IsNotExist(err) {
err = nil
supported = true
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "features: Failed to read io_uring_disabled"),
}
return
}
disabledStr := strings.TrimSpace(string(sysctlData))
disabled, parseErr := strconv.Atoi(disabledStr)
if parseErr != nil {
err = &errortypes.ParseError{
errors.Wrapf(
parseErr,
"features: Failed to parse io_uring_disabled value '%s'",
disabledStr,
),
}
return
}
if disabled == 2 {
return
}
supported = true
return
}
func GetExtUringSupport() (supported bool, err error) {
major, minor, _, err := GetQemuVersion()
if err != nil {
return
}
if major > 10 {
supported = true
}
if major == 10 && minor >= 2 {
supported = true
}
return
}
func GetMemoryBackendSupport() (supported bool, err error) {
major, _, _, err := GetQemuVersion()
if err != nil {
return
}
if major >= 6 {
supported = true
}
return
}
func GetRunWithSupport() (supported bool, err error) {
major, _, _, err := GetQemuVersion()
if err != nil {
return
}
if major >= 9 {
supported = true
}
return
}
================================================
FILE: features/systemd.go
================================================
package features
import (
"strconv"
"strings"
"github.com/pritunl/pritunl-cloud/utils"
)
func GetSystemdVersion() (ver int) {
output, _ := utils.ExecCombinedOutputLogged(
nil,
"/usr/bin/systemctl", "--version",
)
lines := strings.Split(output, "\n")
for _, line := range lines {
if !strings.Contains(line, "systemd") {
continue
}
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
n, err := strconv.Atoi(fields[1])
if err != nil {
continue
}
ver = n
break
}
return
}
func HasSystemdNamespace() bool {
ver := GetSystemdVersion()
if ver >= 243 {
return true
}
return false
}
================================================
FILE: finder/constants.go
================================================
package finder
const (
DomainKind = "domain"
VpcKind = "vpc"
SubnetKind = "subnet"
DatacenterKind = "datacenter"
NodeKind = "node"
PoolKind = "pool"
ZoneKind = "zone"
ShapeKind = "shape"
DiskKind = "disk"
ImageKind = "image"
BuildKind = "build"
InstanceKind = "instance"
FirewallKind = "firewall"
PlanKind = "plan"
CertificateKind = "certificate"
SecretKind = "secret"
PodKind = "pod"
UnitKind = "unit"
JournalKind = "journal"
)
================================================
FILE: finder/resources.go
================================================
package finder
import (
"regexp"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/plan"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/shape"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
)
type Resources struct {
Organization bson.ObjectID
Datacenter *datacenter.Datacenter
Zone *zone.Zone
Vpc *vpc.Vpc
Subnet *vpc.Subnet
Shape *shape.Shape
Node *node.Node
Pool *pool.Pool
Image *image.Image
Disks []*disk.Disk
Instance *instance.Instance
Plan *plan.Plan
Domain *domain.Domain
Certificate *certificate.Certificate
Secret *secret.Secret
Deployment *deployment.Deployment
Pod *PodBase
Unit *UnitBase
Selector string
}
var tokenRe = regexp.MustCompile(
`\+\/([a-zA-Z0-9-]*)\/([a-zA-Z0-9-_.]*)(?:(?:\/|\:)([a-zA-Z0-9-_.]*)(?:\/([a-zA-Z0-9-_.]*))?)?`)
func (r *Resources) Find(db *database.Database, token string) (
kind string, err error) {
matches := tokenRe.FindStringSubmatch(token)
if len(matches) < 3 {
err = &errortypes.ParseError{
errors.Newf("spec: Invalid token '%s'", token),
}
return
}
kind = matches[1]
resource := matches[2]
tag := ""
r.Selector = ""
if len(matches) > 3 {
if strings.Contains(token, ":") {
tag = matches[3]
if len(matches) > 4 {
r.Selector = matches[4]
}
} else {
r.Selector = matches[3]
}
}
switch kind {
case DomainKind:
r.Domain, err = domain.GetOne(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case VpcKind:
query := bson.M{
"name": resource,
"organization": r.Organization,
}
if r.Datacenter != nil {
query["datacenter"] = r.Datacenter.Id
} else if r.Zone != nil {
query["datacenter"] = r.Zone.Datacenter
}
r.Vpc, err = vpc.GetOne(db, &query)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case SubnetKind:
if r.Vpc != nil {
subnet := r.Vpc.GetSubnetName(resource)
r.Subnet = subnet
}
break
case DatacenterKind:
r.Datacenter, err = datacenter.GetOne(db, &bson.M{
"name": resource,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case NodeKind:
r.Node, err = node.GetOne(db, &bson.M{
"name": resource,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case PoolKind:
r.Pool, err = pool.GetOne(db, &bson.M{
"name": resource,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case ZoneKind:
r.Zone, err = zone.GetOne(db, &bson.M{
"name": resource,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
r.Datacenter, err = datacenter.Get(db, r.Zone.Datacenter)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case ShapeKind:
query := bson.M{
"name": resource,
}
if r.Datacenter != nil {
query["datacenter"] = r.Datacenter.Id
} else if r.Zone != nil {
query["datacenter"] = r.Zone.Datacenter
}
r.Shape, err = shape.GetOne(db, &query)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case ImageKind:
if image.Releases.Contains(resource) {
imgs, e := image.GetAll(db, &bson.M{
"release": resource,
"organization": &bson.M{
"$in": []bson.ObjectID{r.Organization, image.Global},
},
})
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
var latestImg *image.Image
for _, img := range imgs {
if latestImg == nil {
latestImg = img
} else if img.Build > latestImg.Build {
latestImg = img
}
if img.Build == tag {
r.Image = img
break
}
}
if latestImg != nil && (tag == "" || tag == "latest") {
r.Image = latestImg
}
}
if r.Image == nil {
r.Image, err = image.GetOne(db, &bson.M{
"name": resource,
"organization": &bson.M{
"$in": []bson.ObjectID{r.Organization, image.Global},
},
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
}
break
case BuildKind:
r.Unit, err = GetUnitBase(db, r.Organization, resource)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if r.Unit != nil {
if tag == "" || tag == "latest" {
deplys, e := deployment.GetAllSorted(db, &bson.M{
"unit": r.Unit.Id,
})
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
for _, deply := range deplys {
r.Deployment = deply
break
}
} else {
deplys, e := deployment.GetAllSorted(db, &bson.M{
"unit": r.Unit.Id,
"tags": tag,
})
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
for _, deply := range deplys {
r.Deployment = deply
break
}
}
}
break
case DiskKind:
r.Disks, err = disk.GetAll(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case InstanceKind:
r.Instance, err = instance.GetOne(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case PlanKind:
r.Plan, err = plan.GetOne(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case CertificateKind:
r.Certificate, err = certificate.GetOne(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case SecretKind:
r.Secret, err = secret.GetOne(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case PodKind:
r.Pod, err = GetPodBase(db, &bson.M{
"name": resource,
"organization": r.Organization,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
case UnitKind:
r.Unit, err = GetUnitBase(db, r.Organization, resource)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
break
default:
err = &errortypes.ParseError{
errors.Newf("spec: Unknown kind '%s'", kind),
}
return
}
return
}
type PodBase struct {
Id bson.ObjectID `bson:"_id,omitempty"`
Organization bson.ObjectID `bson:"organization"`
Name string `bson:"name"`
}
type UnitBase struct {
Id bson.ObjectID `bson:"_id,omitempty"`
Pod bson.ObjectID `bson:"pod"`
Organization bson.ObjectID `bson:"organization"`
Name string `bson:"name"`
}
func GetPodBase(db *database.Database, query *bson.M) (
pd *PodBase, err error) {
coll := db.Pods()
pd = &PodBase{}
err = coll.FindOne(db, query).Decode(pd)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetUnitBase(db *database.Database, orgId bson.ObjectID,
name string) (unt *UnitBase, err error) {
coll := db.Units()
unt = &UnitBase{}
err = coll.FindOne(db, &bson.M{
"name": name,
"organization": orgId,
}).Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: firewall/constants.go
================================================
package firewall
import "github.com/pritunl/mongo-go-driver/v2/bson"
const (
All = "all"
Icmp = "icmp"
Tcp = "tcp"
Udp = "udp"
Multicast = "multicast"
Broadcast = "broadcast"
)
var (
Global = bson.NilObjectID
)
================================================
FILE: firewall/firewall.go
================================================
package firewall
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Rule struct {
SourceIps []string `bson:"source_ips" json:"source_ips"`
Protocol string `bson:"protocol" json:"protocol"`
Port string `bson:"port" json:"port"`
}
type Mapping struct {
Ipvs bool `bson:"ipvs" json:"ipvs"`
Address string `bson:"adress" json:"adress"`
Protocol string `bson:"protocol" json:"protocol"`
ExternalPort int `bson:"external_port" json:"external_port"`
InternalPort int `bson:"internal_port" json:"internal_port"`
}
func (r *Rule) SetName(ipv6 bool) (name string) {
switch r.Protocol {
case All:
if ipv6 {
name = "pr6_all"
} else {
name = "pr4_all"
}
break
case Icmp:
if ipv6 {
name = "pr6_icmp"
} else {
name = "pr4_icmp"
}
break
case Multicast:
if ipv6 {
name = "pr6_multi"
} else {
name = "pr4_multi"
}
break
case Broadcast:
if ipv6 {
name = "pr6_broad"
} else {
name = "pr4_broad"
}
break
case Tcp, Udp:
if ipv6 {
name = fmt.Sprintf(
"pr6_%s_%s",
r.Protocol,
strings.Replace(r.Port, "-", "_", 1),
)
} else {
name = fmt.Sprintf(
"pr4_%s_%s",
r.Protocol,
strings.Replace(r.Port, "-", "_", 1),
)
}
break
default:
break
}
return
}
type Firewall struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Roles []string `bson:"roles" json:"roles"`
Ingress []*Rule `bson:"ingress" json:"ingress"`
}
func (f *Firewall) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
f.Name = utils.FilterName(f.Name)
if f.Roles == nil {
f.Roles = []string{}
}
if f.Ingress == nil {
f.Ingress = []*Rule{}
}
for _, rule := range f.Ingress {
switch rule.Protocol {
case All:
rule.Port = ""
break
case Icmp:
rule.Port = ""
break
case Tcp, Udp, Multicast, Broadcast:
ports := strings.Split(rule.Port, "-")
portInt, e := strconv.Atoi(ports[0])
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
if portInt < 1 || portInt > 65535 {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
parsedPort := strconv.Itoa(portInt)
if len(ports) > 1 {
portInt2, e := strconv.Atoi(ports[1])
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
if portInt < 1 || portInt > 65535 || portInt2 <= portInt {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
parsedPort += "-" + strconv.Itoa(portInt2)
}
rule.Port = parsedPort
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_protocol",
Message: "Invalid ingress rule protocol",
}
return
}
if rule.Protocol == Multicast || rule.Protocol == Broadcast {
rule.SourceIps = []string{}
} else {
for i, sourceIp := range rule.SourceIps {
if sourceIp == "" {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_source_ip",
Message: "Empty ingress rule source IP",
}
return
}
if !strings.Contains(sourceIp, "/") {
if strings.Contains(sourceIp, ":") {
sourceIp += "/128"
} else {
sourceIp += "/32"
}
}
_, sourceCidr, e := net.ParseCIDR(sourceIp)
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_source_ip",
Message: "Invalid ingress rule source IP",
}
return
}
rule.SourceIps[i] = sourceCidr.String()
}
}
}
return
}
func (f *Firewall) Commit(db *database.Database) (err error) {
coll := db.Firewalls()
err = coll.Commit(f.Id, f)
if err != nil {
return
}
return
}
func (f *Firewall) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Firewalls()
err = coll.CommitFields(f.Id, f, fields)
if err != nil {
return
}
return
}
func (f *Firewall) Insert(db *database.Database) (err error) {
coll := db.Firewalls()
if !f.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("firewall: Firewall already exists"),
}
return
}
resp, err := coll.InsertOne(db, f)
if err != nil {
err = database.ParseError(err)
return
}
f.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: firewall/spec.go
================================================
package firewall
import (
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/vm"
)
func GetSpecRules(instances []*instance.Instance,
deploymentsNode map[bson.ObjectID]*deployment.Deployment,
specsMap map[bson.ObjectID]*spec.Spec,
specsUnitsMap map[bson.ObjectID]*unit.Unit,
deploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment) (
firewalls map[string][]*Rule, err error) {
firewalls = map[string][]*Rule{}
for _, inst := range instances {
if inst.Deployment.IsZero() {
continue
}
deply := deploymentsNode[inst.Deployment]
if deply == nil {
continue
}
spc := specsMap[deply.Spec]
if spc == nil {
continue
}
if spc.Firewall == nil || spc.Firewall.Ingress == nil {
continue
}
if !inst.IsActive() {
continue
}
namespaces := []string{}
for i := range inst.Virt.NetworkAdapters {
namespaces = append(namespaces, vm.GetNamespace(inst.Id, i))
}
for _, specRule := range spc.Firewall.Ingress {
rule := &Rule{
Protocol: specRule.Protocol,
Port: specRule.Port,
SourceIps: specRule.SourceIps,
}
for _, ref := range specRule.Sources {
if ref.Kind != spec.Unit {
continue
}
ruleUnit := specsUnitsMap[ref.Id]
if ruleUnit == nil {
continue
}
for _, ruleDeplyId := range ruleUnit.Deployments {
ruleDeply := deploymentsDeployedMap[ruleDeplyId]
if ruleDeply == nil {
continue
}
instData := ruleDeply.InstanceData
if instData == nil {
continue
}
switch ref.Selector {
case "", "private_ips":
for _, ip := range instData.PrivateIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "private_ips6":
for _, ip := range instData.PrivateIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "public_ips":
for _, ip := range instData.PublicIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "public_ips6":
for _, ip := range instData.PublicIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_private_ips":
for _, ip := range instData.CloudPrivateIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_public_ips":
for _, ip := range instData.CloudPublicIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_public_ips6":
for _, ip := range instData.CloudPublicIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "host_ips":
for _, ip := range instData.HostIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
}
}
}
if len(rule.SourceIps) == 0 {
continue
}
for _, namespace := range namespaces {
firewalls[namespace] = append(firewalls[namespace], rule)
}
}
}
return
}
func GetSpecRulesSlow(db *database.Database,
nodeId bson.ObjectID, instances []*instance.Instance) (
firewalls map[string][]*Rule, nodePortsMap map[string][]*nodeport.Mapping,
err error) {
deployments, err := deployment.GetAll(db, &bson.M{
"node": nodeId,
})
if err != nil {
return
}
deploymentsNode := map[bson.ObjectID]*deployment.Deployment{}
deploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsIdSet := set.NewSet()
podIdsSet := set.NewSet()
unitIdsSet := set.NewSet()
specIdsSet := set.NewSet()
for _, deply := range deployments {
deploymentsNode[deply.Id] = deply
deploymentsIdSet.Add(deply.Id)
if deply.State == deployment.Deployed {
deploymentsDeployedMap[deply.Id] = deply
}
podIdsSet.Add(deply.Pod)
unitIdsSet.Add(deply.Unit)
specIdsSet.Add(deply.Spec)
}
specIds := []bson.ObjectID{}
for specId := range specIdsSet.Iter() {
specIds = append(specIds, specId.(bson.ObjectID))
}
specs, err := spec.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specIds,
},
})
if err != nil {
return
}
specUnitsSet := set.NewSet()
specsMap := map[bson.ObjectID]*spec.Spec{}
for _, spc := range specs {
specsMap[spc.Id] = spc
if spc.Firewall != nil {
for _, rule := range spc.Firewall.Ingress {
for _, ref := range rule.Sources {
specUnitsSet.Add(ref.Id)
}
}
}
}
specUnitIds := []bson.ObjectID{}
for unitId := range specUnitsSet.Iter() {
specUnitIds = append(specUnitIds, unitId.(bson.ObjectID))
}
specUnits, err := unit.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specUnitIds,
},
})
if err != nil {
return
}
specDeploymentsSet := set.NewSet()
specsUnitsMap := map[bson.ObjectID]*unit.Unit{}
for _, specUnit := range specUnits {
specsUnitsMap[specUnit.Id] = specUnit
for _, deplyId := range specUnit.Deployments {
specDeploymentsSet.Add(deplyId)
}
}
specDeploymentIds := []bson.ObjectID{}
for deplyIdInf := range specDeploymentsSet.Iter() {
deplyId := deplyIdInf.(bson.ObjectID)
if !deploymentsIdSet.Contains(deplyId) {
specDeploymentIds = append(specDeploymentIds, deplyId)
}
}
specDeployments, err := deployment.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specDeploymentIds,
},
})
if err != nil {
return
}
for _, specDeployment := range specDeployments {
deploymentsIdSet.Add(specDeployment.Id)
if specDeployment.State == deployment.Deployed {
deploymentsDeployedMap[specDeployment.Id] = specDeployment
}
}
podIds := []bson.ObjectID{}
for podId := range podIdsSet.Iter() {
podIds = append(podIds, podId.(bson.ObjectID))
}
pods, err := pod.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": podIds,
},
})
if err != nil {
return
}
podsMap := map[bson.ObjectID]*pod.Pod{}
for _, pd := range pods {
podsMap[pd.Id] = pd
}
unitIds := []bson.ObjectID{}
for unitId := range unitIdsSet.Iter() {
unitIds = append(unitIds, unitId.(bson.ObjectID))
}
units := []*unit.Unit{}
if len(unitIds) > 0 {
units, err = unit.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": unitIds,
},
})
if err != nil {
return
}
}
unitsMap := map[bson.ObjectID]*unit.Unit{}
podDeploymentsSet := set.NewSet()
for _, unt := range units {
unitsMap[unt.Id] = unt
for _, deplyId := range unt.Deployments {
podDeploymentsSet.Add(deplyId)
}
}
podDeploymentIds := []bson.ObjectID{}
for deplyIdInf := range podDeploymentsSet.Iter() {
deplyId := deplyIdInf.(bson.ObjectID)
if !deploymentsIdSet.Contains(deplyId) {
podDeploymentIds = append(podDeploymentIds, deplyId)
}
}
podDeployments, err := deployment.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": podDeploymentIds,
},
})
if err != nil {
return
}
for _, podDeployment := range podDeployments {
deploymentsIdSet.Add(podDeployment.Id)
if podDeployment.State == deployment.Deployed {
deploymentsDeployedMap[podDeployment.Id] = podDeployment
}
}
nodePortsMap = map[string][]*nodeport.Mapping{}
firewalls = map[string][]*Rule{}
for _, inst := range instances {
nodePortsMap[inst.NetworkNamespace] = append(
nodePortsMap[inst.NetworkNamespace], inst.NodePorts...)
if inst.Deployment.IsZero() {
continue
}
deply := deploymentsNode[inst.Deployment]
if deply == nil {
continue
}
spc := specsMap[deply.Spec]
if spc == nil {
continue
}
if spc.Firewall == nil || spc.Firewall.Ingress == nil {
continue
}
if !inst.IsActive() {
continue
}
namespaces := []string{}
for i := range inst.Virt.NetworkAdapters {
namespaces = append(namespaces, vm.GetNamespace(inst.Id, i))
}
for _, specRule := range spc.Firewall.Ingress {
rule := &Rule{
Protocol: specRule.Protocol,
Port: specRule.Port,
SourceIps: specRule.SourceIps,
}
for _, ref := range specRule.Sources {
if ref.Kind != spec.Unit {
continue
}
ruleUnit := specsUnitsMap[ref.Id]
if ruleUnit == nil {
continue
}
for _, ruleDeplyId := range ruleUnit.Deployments {
ruleDeply := deploymentsDeployedMap[ruleDeplyId]
if ruleDeply == nil {
continue
}
instData := ruleDeply.InstanceData
if instData == nil {
continue
}
switch ref.Selector {
case "", "private_ips":
for _, ip := range instData.PrivateIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "private_ips6":
for _, ip := range instData.PrivateIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "public_ips":
for _, ip := range instData.PublicIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "public_ips6":
for _, ip := range instData.PublicIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_private_ips":
for _, ip := range instData.CloudPrivateIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_public_ips":
for _, ip := range instData.CloudPublicIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "cloud_public_ips6":
for _, ip := range instData.CloudPublicIps6 {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
case "host_ips":
for _, ip := range instData.HostIps {
rule.SourceIps = append(
rule.SourceIps,
strings.Split(ip, "/")[0]+"/32",
)
}
}
}
}
if len(rule.SourceIps) == 0 {
continue
}
for _, namespace := range namespaces {
firewalls[namespace] = append(firewalls[namespace], rule)
}
}
}
return
}
================================================
FILE: firewall/utils.go
================================================
package firewall
import (
"fmt"
"sort"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
func Get(db *database.Database, fireId bson.ObjectID) (
fire *Firewall, err error) {
coll := db.Firewalls()
fire = &Firewall{}
err = coll.FindOneId(fireId, fire)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, fireId bson.ObjectID) (
fire *Firewall, err error) {
coll := db.Firewalls()
fire = &Firewall{}
err = coll.FindOne(db, &bson.M{
"_id": fireId,
"organization": orgId,
}).Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
fires []*Firewall, err error) {
coll := db.Firewalls()
fires = []*Firewall{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
fires = append(fires, fire)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetRoles(db *database.Database, roles []string) (
fires []*Firewall, err error) {
coll := db.Firewalls()
fires = []*Firewall{}
cursor, err := coll.Find(
db,
&bson.M{
"organization": Global,
"roles": &bson.M{
"$in": roles,
},
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
fires = append(fires, fire)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetMapRoles(db *database.Database, roles []string) (
fires map[string][]*Firewall, err error) {
coll := db.Firewalls()
fires = map[string][]*Firewall{}
cursor, err := coll.Find(db, &bson.M{
"roles": &bson.M{
"$in": roles,
},
})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
for _, role := range fire.Roles {
roleFires := fires[role]
if roleFires == nil {
roleFires = []*Firewall{}
}
fires[role] = append(roleFires, fire)
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrgMapRoles(db *database.Database, orgId bson.ObjectID) (
fires map[string][]*Firewall, err error) {
coll := db.Firewalls()
fires = map[string][]*Firewall{}
cursor, err := coll.Find(db, &bson.M{
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
for _, role := range fire.Roles {
roleFires := fires[role]
if roleFires == nil {
roleFires = []*Firewall{}
}
fires[role] = append(roleFires, fire)
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrgRoles(db *database.Database, orgId bson.ObjectID,
roles []string) (fires []*Firewall, err error) {
coll := db.Firewalls()
fires = []*Firewall{}
cursor, err := coll.Find(
db,
&bson.M{
"organization": orgId,
"roles": &bson.M{
"$in": roles,
},
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
fires = append(fires, fire)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (fires []*Firewall, count int64, err error) {
coll := db.Firewalls()
fires = []*Firewall{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
fire := &Firewall{}
err = cursor.Decode(fire)
if err != nil {
err = database.ParseError(err)
return
}
fires = append(fires, fire)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, fireId bson.ObjectID) (err error) {
coll := db.Firewalls()
_, err = coll.DeleteOne(db, &bson.M{
"_id": fireId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveOrg(db *database.Database, orgId, fireId bson.ObjectID) (
err error) {
coll := db.Firewalls()
_, err = coll.DeleteOne(db, &bson.M{
"_id": fireId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, fireIds []bson.ObjectID) (
err error) {
coll := db.Firewalls()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": fireIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
fireIds []bson.ObjectID) (err error) {
coll := db.Firewalls()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": fireIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func MergeIngress(fires []*Firewall) (rules []*Rule) {
rules = []*Rule{}
rulesMap := map[string]*Rule{}
rulesKey := []string{}
for _, fire := range fires {
for _, ingress := range fire.Ingress {
key := fmt.Sprintf("%s-%s", ingress.Protocol, ingress.Port)
rule := rulesMap[key]
if rule == nil {
rule = &Rule{
Protocol: ingress.Protocol,
Port: ingress.Port,
SourceIps: ingress.SourceIps,
}
rulesMap[key] = rule
rulesKey = append(rulesKey, key)
} else {
sourceIps := set.NewSet()
for _, sourceIp := range rule.SourceIps {
sourceIps.Add(sourceIp)
}
for _, sourceIp := range ingress.SourceIps {
if sourceIps.Contains(sourceIp) {
continue
}
sourceIps.Add(sourceIp)
rule.SourceIps = append(rule.SourceIps, sourceIp)
}
}
}
}
sort.Strings(rulesKey)
for _, key := range rulesKey {
rules = append(rules, rulesMap[key])
}
return
}
func GetAllIngress(db *database.Database, nodeSelf *node.Node,
instances []*instance.Instance, specRules map[string][]*Rule,
nodePortsMap map[string][]*nodeport.Mapping) (
nodeFirewall []*Rule, firewalls map[string][]*Rule,
mappings map[string][]*Mapping,
instNamespaces map[bson.ObjectID][]string, err error) {
if nodeSelf.Firewall {
fires, e := GetRoles(db, nodeSelf.Roles)
if e != nil {
err = e
return
}
ingress := MergeIngress(fires)
nodeFirewall = ingress
}
instNamespaces = map[bson.ObjectID][]string{}
nodePortIps := map[string]string{}
firewalls = map[string][]*Rule{}
for _, inst := range instances {
if !inst.IsActive() {
continue
}
namespaces := []string{}
for i := range inst.Virt.NetworkAdapters {
namespaces = append(namespaces, vm.GetNamespace(inst.Id, i))
}
instNamespaces[inst.Id] = namespaces
if len(inst.NodePortIps) > 0 && len(namespaces) > 0 {
nodePortIps[namespaces[0]] = inst.NodePortIps[0]
}
fires, e := GetOrgRoles(db,
inst.Organization, inst.Roles)
if e != nil {
err = e
return
}
ingress := MergeIngress(fires)
for _, namespace := range namespaces {
_, ok := firewalls[namespace]
if ok {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"namespace": namespace,
}).Error("firewall: Namespace conflict")
err = &errortypes.ParseError{
errors.New("firewall: Namespace conflict"),
}
return
}
firewalls[namespace] = ingress
}
}
for namespace, rules := range specRules {
firewalls[namespace] = append(firewalls[namespace], rules...)
}
mappings = map[string][]*Mapping{}
externalPorts := map[int]string{}
for namespace, ndePorts := range nodePortsMap {
for _, ndePort := range ndePorts {
ipvs := false
extNamespace := externalPorts[ndePort.ExternalPort]
if extNamespace != "" {
ipvs = true
if extNamespace != "-" {
for _, mapping := range mappings[extNamespace] {
if mapping.ExternalPort == ndePort.ExternalPort {
mapping.Ipvs = true
}
}
externalPorts[ndePort.ExternalPort] = "-"
}
} else {
externalPorts[ndePort.ExternalPort] = namespace
}
mappings[namespace] = append(mappings[namespace], &Mapping{
Ipvs: ipvs,
Address: nodePortIps[namespace],
Protocol: ndePort.Protocol,
ExternalPort: ndePort.ExternalPort,
InternalPort: ndePort.InternalPort,
})
}
}
return
}
func GetAllIngressPreloaded(nodeSelf *node.Node,
instances []*instance.Instance, specRules map[string][]*Rule,
nodePortsMap map[string][]*nodeport.Mapping,
firesMap map[string][]*Firewall) (
nodeFirewall []*Rule, firewalls map[string][]*Rule,
mappings map[string][]*Mapping,
instNamespaces map[bson.ObjectID][]string, err error) {
if nodeSelf.Firewall {
fires := []*Firewall{}
for _, role := range nodeSelf.Roles {
for _, fire := range firesMap[role] {
if fire.Organization == Global {
fires = append(fires, fire)
}
}
}
ingress := MergeIngress(fires)
nodeFirewall = ingress
}
instNamespaces = map[bson.ObjectID][]string{}
nodePortIps := map[string]string{}
firewalls = map[string][]*Rule{}
for _, inst := range instances {
if !inst.IsActive() {
continue
}
namespaces := []string{}
for i := range inst.Virt.NetworkAdapters {
namespaces = append(namespaces, vm.GetNamespace(inst.Id, i))
}
instNamespaces[inst.Id] = namespaces
if len(inst.NodePortIps) > 0 && len(namespaces) > 0 {
nodePortIps[namespaces[0]] = inst.NodePortIps[0]
}
fires := []*Firewall{}
for _, role := range inst.Roles {
for _, fire := range firesMap[role] {
if fire.Organization == inst.Organization {
fires = append(fires, fire)
}
}
}
ingress := MergeIngress(fires)
for _, namespace := range namespaces {
_, ok := firewalls[namespace]
if ok {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"namespace": namespace,
}).Error("firewall: Namespace conflict")
err = &errortypes.ParseError{
errors.New("firewall: Namespace conflict"),
}
return
}
firewalls[namespace] = ingress
}
}
for namespace, rules := range specRules {
firewalls[namespace] = append(firewalls[namespace], rules...)
}
mappings = map[string][]*Mapping{}
externalPorts := map[int]string{}
for namespace, ndePorts := range nodePortsMap {
for _, ndePort := range ndePorts {
ipvs := false
extNamespace := externalPorts[ndePort.ExternalPort]
if extNamespace != "" {
ipvs = true
if extNamespace != "-" {
for _, mapping := range mappings[extNamespace] {
if mapping.ExternalPort == ndePort.ExternalPort {
mapping.Ipvs = true
}
}
externalPorts[ndePort.ExternalPort] = "-"
}
} else {
externalPorts[ndePort.ExternalPort] = namespace
}
mappings[namespace] = append(mappings[namespace], &Mapping{
Ipvs: ipvs,
Address: nodePortIps[namespace],
Protocol: ndePort.Protocol,
ExternalPort: ndePort.ExternalPort,
InternalPort: ndePort.InternalPort,
})
}
}
return
}
================================================
FILE: geo/geo.go
================================================
package geo
import (
"bytes"
"encoding/json"
"net/http"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
client = &http.Client{
Timeout: 10 * time.Second,
}
)
type Geo struct {
Address string `bson:"_id" json:"address"`
Isp string `bson:"i" json:"isp"`
Continent string `bson:"z" json:"continent"`
ContinentCode string `bson:"q" json:"continent_code"`
Country string `bson:"c" json:"country"`
CountryCode string `bson:"w" json:"country_code"`
Region string `bson:"r" json:"region"`
RegionCode string `bson:"e" json:"region_code"`
City string `bson:"a" json:"city"`
Longitude float64 `bson:"x" json:"longitude"`
Latitude float64 `bson:"y" json:"latitude"`
Timestamp time.Time `bson:"t" json:"-"`
}
type geoData struct {
License string `json:"license"`
Address string `json:"address"`
}
func get(addr string) (ge *Geo, err error) {
if settings.System.License == "" {
return
}
reqGeoData := &geoData{
License: settings.System.License,
Address: addr,
}
reqData := &bytes.Buffer{}
err = json.NewEncoder(reqData).Encode(reqGeoData)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "geo: Failed to parse request data"),
}
return
}
req, err := http.NewRequest(
"GET",
settings.Auth.Server+"/geo",
reqData,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "geo: Failed to create request"),
}
return
}
resp, err := client.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "geo: Failed to send request"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "geo: Geo request error")
if err != nil {
return
}
ge = &Geo{}
err = json.NewDecoder(resp.Body).Decode(ge)
if err != nil {
ge = nil
err = &errortypes.ParseError{
errors.Wrap(err, "geo: Failed to parse response"),
}
return
}
return
}
func Get(db *database.Database, addr string) (ge *Geo, err error) {
ge = &Geo{}
coll := db.Geo()
err = coll.FindOneId(addr, ge)
if err != nil {
switch err.(type) {
case *database.NotFoundError:
ge = nil
err = nil
default:
return
}
}
if ge == nil {
ge, err = get(addr)
if err != nil {
return
}
if ge != nil {
ge.Timestamp = time.Now()
_, _ = coll.InsertOne(db, ge)
} else {
ge = &Geo{}
}
}
return
}
================================================
FILE: go.mod
================================================
module github.com/pritunl/pritunl-cloud
go 1.25.9
require (
github.com/aws/aws-sdk-go v1.55.5
github.com/cloudflare/cloudflare-go v0.104.0
github.com/coredns/coredns v1.14.3
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd
github.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7
github.com/gin-gonic/gin v1.10.0
github.com/go-webauthn/webauthn v0.12.3
github.com/google/uuid v1.6.0
github.com/gorilla/securecookie v1.1.2
github.com/gorilla/sessions v1.4.0
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f
github.com/mdlayher/ndp v1.1.0
github.com/miekg/dns v1.1.72
github.com/minio/minio-go/v7 v7.0.76
github.com/oracle/oci-go-sdk/v65 v65.74.0
github.com/pritunl/mongo-go-driver/v2 v2.3.0
github.com/pritunl/tools v1.2.5
github.com/sirupsen/logrus v1.9.3
github.com/twilio/twilio-go v1.23.0
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90
golang.org/x/crypto v0.50.0
golang.org/x/net v0.53.0
golang.org/x/oauth2 v0.36.0
golang.org/x/sys v0.43.0
google.golang.org/api v0.276.0
gopkg.in/yaml.v2 v2.4.0
)
require (
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/apparentlymart/go-cidr v1.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.12.2 // indirect
github.com/bytedance/sonic/loader v0.2.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/coredns/caddy v1.1.4 // indirect
github.com/dnstap/golang-dnstap v0.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/farsightsec/golang-framestream v0.3.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
github.com/fxamacker/cbor/v2 v2.9.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.22.1 // indirect
github.com/go-webauthn/x v0.1.20 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/gofrs/flock v0.13.0 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/mock v1.7.0-rc.1 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/go-tpm v0.9.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.5 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.6.0 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opentracing/opentracing-go v1.2.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pierrec/lz4/v4 v4.1.22 // indirect
github.com/pires/go-proxyproto v0.12.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.20.1 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/sony/gobreaker v1.0.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.2 // indirect
github.com/xdg-go/stringprep v1.0.4 // indirect
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect
go.opentelemetry.io/otel v1.43.0 // indirect
go.opentelemetry.io/otel/metric v1.43.0 // indirect
go.opentelemetry.io/otel/trace v1.43.0 // indirect
go.uber.org/mock v0.6.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/arch v0.10.0 // indirect
golang.org/x/mod v0.35.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.44.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 // indirect
google.golang.org/grpc v1.80.0 // indirect
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
================================================
FILE: go.sum
================================================
cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA=
cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
github.com/apparentlymart/go-cidr v1.1.1 h1:oEEk8CE0HP0YpHxsegk/TaOtR2FLHdWv4p3eM4ceUwg=
github.com/apparentlymart/go-cidr v1.1.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU=
github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg=
github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/cloudflare-go v0.104.0 h1:R/lB0dZupaZbOgibAH/BRrkFbZ6Acn/WsKg2iX2xXuY=
github.com/cloudflare/cloudflare-go v0.104.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/coredns/caddy v1.1.4 h1:+Lls5xASB0QsA2jpCroCOwpPlb5GjIGlxdjXxdX0XVo=
github.com/coredns/caddy v1.1.4/go.mod h1:A6ntJQlAWuQfFlsd9hvigKbo2WS0VUs2l1e2F+BawD4=
github.com/coredns/coredns v1.14.3 h1:hWWoTdONblKIWhC8QPkxLEGIbewhR5xyTedqLVPsvvE=
github.com/coredns/coredns v1.14.3/go.mod h1:15BWsGGxupagKQ3p09pIIZ5kcgmyawquey6gqNWRaEI=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dnstap/golang-dnstap v0.4.0 h1:KRHBoURygdGtBjDI2w4HifJfMAhhOqDuktAokaSa234=
github.com/dnstap/golang-dnstap v0.4.0/go.mod h1:FqsSdH58NAmkAvKcpyxht7i4FoBjKu8E4JUPt8ipSUs=
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd h1:s2vYw+2c+7GR1ccOaDuDcKsmNB/4RIxyu5liBm1VRbs=
github.com/dropbox/godropbox v0.0.0-20230623171840-436d2007a9fd/go.mod h1:Vr/Q4p40Kce7JAHDITjDhiy/zk07W4tqD5YVi5FD0PA=
github.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7 h1:2QX96efe1AvKmqAdqeAn3efxI3lr+EULVbzRxZ/rKGQ=
github.com/duosecurity/duo_api_golang v0.0.0-20250430191550-ac36954387e7/go.mod h1:hJ6IPTuCAvWv+i9ubnPZB3VpVRuj/+SAblWFcI0mjEU=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/farsightsec/golang-framestream v0.3.0 h1:/spFQHucTle/ZIPkYqrfshQqPe2VQEzesH243TjIwqA=
github.com/farsightsec/golang-framestream v0.3.0/go.mod h1:eNde4IQyEiA5br02AouhEHCu3p3UzrCdFR4LuQHklMI=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fxamacker/cbor/v2 v2.9.1 h1:2rWm8B193Ll4VdjsJY28jxs70IdDsHRWgQYAI80+rMQ=
github.com/fxamacker/cbor/v2 v2.9.1/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-webauthn/webauthn v0.12.3 h1:hHQl1xkUuabUU9uS+ISNCMLs9z50p9mDUZI/FmkayNE=
github.com/go-webauthn/webauthn v0.12.3/go.mod h1:4JRe8Z3W7HIw8NGEWn2fnUwecoDzkkeach/NnvhkqGY=
github.com/go-webauthn/x v0.1.20 h1:brEBDqfiPtNNCdS/peu8gARtq8fIPsHz0VzpPjGvgiw=
github.com/go-webauthn/x v0.1.20/go.mod h1:n/gAc8ssZJGATM0qThE+W+vfgXiMedsWi3wf/C4lld0=
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U=
github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs=
github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/go-tpm v0.9.3 h1:+yx0/anQuGzi+ssRqeD6WpXjW2L/V0dItUayO0i9sRc=
github.com/google/go-tpm v0.9.3/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas=
github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg=
github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4=
github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f h1:dd33oobuIv9PcBVqvbEiCXEbNTomOHyj3WFuC5YiPRU=
github.com/insomniacslk/dhcp v0.0.0-20250417080101-5f8cf70e8c5f/go.mod h1:zhFlBeJssZ1YBCMZ5Lzu1pX4vhftDvU10WUVb1uXKtM=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275 h1:IZycmTpoUtQK3PD60UYBwjaCUHUP7cML494ao9/O8+Q=
github.com/localtunnel/go-localtunnel v0.0.0-20170326223115-8a804488f275/go.mod h1:zt6UU74K6Z6oMOYJbJzYpYucqdcQwSMPBEdSvGiaUMw=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mdlayher/ndp v1.1.0 h1:QylGKGVtH60sKZUE88+IW5ila1Z/M9/OXhWdsVKuscs=
github.com/mdlayher/ndp v1.1.0/go.mod h1:FmgESgemgjl38vuOIyAHWUUL6vQKA/pQNkvXdWsdQFM=
github.com/mdlayher/packet v1.1.2 h1:3Up1NG6LZrsgDVn6X4L9Ge/iyRyxFEFD9o6Pr3Q1nQY=
github.com/mdlayher/packet v1.1.2/go.mod h1:GEu1+n9sG5VtiRE4SydOmX5GTwyyYlteZiFU+x0kew4=
github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU=
github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/miekg/dns v1.1.72 h1:vhmr+TF2A3tuoGNkLDFK9zi36F2LS+hKTRW0Uf8kbzI=
github.com/miekg/dns v1.1.72/go.mod h1:+EuEPhdHOsfk6Wk5TT2CzssZdqkmFhf8r+aVyDEToIs=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.76 h1:9nxHH2XDai61cT/EFhyIw/wW4vJfpPNvl7lSFpRt+Ng=
github.com/minio/minio-go/v7 v7.0.76/go.mod h1:AVM3IUN6WwKzmwBxVdjzhH8xq+f57JSbbvzqvUzR6eg=
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c h1:cqn374mizHuIWj+OSJCajGr/phAmuMug9qIX3l9CflE=
github.com/mitchellh/mapstructure v1.5.1-0.20231216201459-8508981c8b6c/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/oracle/oci-go-sdk/v65 v65.74.0 h1:oA2VXpecSTwc45QJGsKNoxCBwbUMuXLQ2W4pLZZarro=
github.com/oracle/oci-go-sdk/v65 v65.74.0/go.mod h1:IBEV9l1qBzUpo7zgGaRUhbB05BVfcDGYRFBCPlTcPp0=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pires/go-proxyproto v0.12.0 h1:TTCxD66dU898tahivkqc3hoceZp7P44FnorWyo9d5vM=
github.com/pires/go-proxyproto v0.12.0/go.mod h1:qUvfqUMEoX7T8g0q7TQLDnhMjdTrxnG0hvpMn+7ePNI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pritunl/mongo-go-driver/v2 v2.3.0 h1:ZQ8ZujYwwY+1TEXwf8pGytdprmgGpe/HRP7fa4xbRow=
github.com/pritunl/mongo-go-driver/v2 v2.3.0/go.mod h1:U8W2Evrwe7KTE6DFgMV1Ub/WLXzJht/lyXnLHEK5E+8=
github.com/pritunl/tools v1.2.5 h1:qbVj1QQdhhgQLBa2JnjBlVbPJt/t33veI/QN+kJq28s=
github.com/pritunl/tools v1.2.5/go.mod h1:BiNzTb2ZCesQ5k/Mx0mhOwGXNJNdZk+4jqg39GjRXKU=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc=
github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo=
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw=
github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sony/gobreaker v0.5.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sony/gobreaker v1.0.0 h1:feX5fGGXSl3dYd4aHZItw+FpHLvvoaqkawKjVNiFMNQ=
github.com/sony/gobreaker v1.0.0/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/twilio/twilio-go v1.23.0 h1:cIJD6XnVuRqnMVp8LswoOTEi4/JK9WctOTUvUR2gLf0=
github.com/twilio/twilio-go v1.23.0/go.mod h1:zRkMjudW7v7MqQ3cWNZmSoZJ7EBjPZ4OpNh2zm7Q6ko=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90 h1:rB0J+hLNltG1Qv+UF+MkdFz89XMps5BOAFJN4xWjc+s=
github.com/ua-parser/uap-go v0.0.0-20250213224047-9c035f085b90/go.mod h1:BUbeWZiieNxAuuADTBNb3/aeje6on3DhU3rpWsQSB1E=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo=
go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I=
go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0=
go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM=
go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY=
go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg=
go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg=
go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw=
go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A=
go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A=
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=
golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY=
google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0=
google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I=
google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4 h1:yOzSCGPx+cp5VO7IxvZ9SBFF7j1tZVcNtlHR2iYKtVo=
google.golang.org/genproto/googleapis/api v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:Q9HWtNeE7tM9npdIsEvqXj1QJIvVoeAV3rtXtS715Cw=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4 h1:tEkOQcXgF6dH1G+MVKZrfpYvozGrzb91k6ha7jireSM=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260427160629-7cedc36a6bc4/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM=
google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI=
google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
================================================
FILE: guest/guest.go
================================================
package guest
import (
"time"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
socketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)
)
type Command struct {
Execute string `json:"execute"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
}
type Response struct {
Return map[string]interface{} `json:"return"`
Error map[string]interface{} `json:"error,omitempty"`
RawData []byte `json:"-"`
}
================================================
FILE: guest/power.go
================================================
package guest
import (
"encoding/json"
"net"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/utils"
)
func Shutdown(vmId bson.ObjectID) (err error) {
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
sockPath := paths.GetGuestPath(vmId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if !exists {
err = &errortypes.ReadError{
errors.New("guest: Guest agent socket missing"),
}
return
}
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "guest: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "guest: Failed set deadline"),
}
return
}
cmd := Command{
Execute: "guest-shutdown",
Arguments: map[string]interface{}{
"mode": "powerdown",
},
}
cmdData, err := json.Marshal(cmd)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "guest: Failed to marshal socket data"),
}
return
}
cmdData = append(cmdData, '\n')
_, err = conn.Write(cmdData)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "guest: Failed to write socket"),
}
return
}
buffer := make([]byte, 8192)
n, err := conn.Read(buffer)
if err != nil {
err = nil
return
}
var response Response
response.RawData = buffer[:n]
err = json.Unmarshal(response.RawData, &response)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "guest: Failed to parse socket data"),
}
return
}
if response.Error != nil {
err = &errortypes.ReadError{
errors.Newf("guest: Guest returned error %v", response.Error),
}
return
}
return
}
================================================
FILE: hnetwork/hnetwork.go
================================================
package hnetwork
import (
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/state"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
var (
initialized = false
curGateway = ""
curRule *IptablesRule
)
type IptablesRule struct {
Source string
Output string
}
func (h *IptablesRule) Add() (err error) {
args := []string{
"-t", "nat",
"-A", "POSTROUTING",
}
if h.Source != "" {
args = append(args, "-s", h.Source)
}
if h.Output != "" {
args = append(args, "-o", h.Output)
}
args = append(args,
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "MASQUERADE",
)
_, err = utils.ExecCombinedOutputLogged(
[]string{
"matching rule exist",
"match by that name",
},
"iptables",
args...,
)
if err != nil {
return
}
if err != nil {
return
}
return
}
func (h *IptablesRule) Remove() (err error) {
args := []string{
"-t", "nat",
"-D", "POSTROUTING",
}
if h.Source != "" {
args = append(args, "-s", h.Source)
}
if h.Output != "" {
args = append(args, "-o", h.Output)
}
args = append(args,
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "MASQUERADE",
)
_, err = utils.ExecCombinedOutputLogged(
[]string{
"matching rule exist",
"match by that name",
},
"iptables",
args...,
)
if err != nil {
return
}
return
}
func loadIptablesNat() (rules []*IptablesRule, err error) {
rules = []*IptablesRule{}
output, err := utils.ExecOutput("", "iptables", "-t", "nat", "-S")
if err != nil {
return
}
for _, line := range strings.Split(output, "\n") {
if !strings.Contains(line, "POSTROUTING") ||
!strings.Contains(line, "MASQUERADE") ||
!strings.Contains(line, "pritunl_cloud_host_nat") {
continue
}
cmd := strings.Fields(line)
cmdLen := len(cmd)
if cmdLen < 3 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("hnetwork: Invalid iptables state")
err = &errortypes.ParseError{
errors.New("hnetwork: Invalid iptables state"),
}
return
}
rule := &IptablesRule{}
for i, item := range cmd {
if item == "-s" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("hnetwork: Invalid iptables host nat source")
err = &errortypes.ParseError{
errors.New(
"hnetwork: Invalid iptables host nat source"),
}
return
}
rule.Source = cmd[i+1]
}
if item == "-o" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("hnetwork: Invalid iptables host nat output")
err = &errortypes.ParseError{
errors.New(
"hnetwork: Invalid iptables host nat output"),
}
return
}
rule.Output = cmd[i+1]
}
}
rules = append(rules, rule)
}
return
}
func removeNetwork(stat *state.State) (err error) {
if curGateway != "" || stat.HasInterfaces(
settings.Hypervisor.HostNetworkName) {
err = clearAddr()
if err != nil {
return
}
curGateway = ""
}
return
}
func ApplyState(stat *state.State) (err error) {
initializeInst := false
hostNetName := settings.Hypervisor.HostNetworkName
if !initialized {
addr, e := getAddr()
if e != nil {
err = e
return
}
rules, e := loadIptablesNat()
if e != nil {
err = e
return
}
if len(rules) > 1 {
for _, rule := range rules {
err = rule.Remove()
if err != nil {
return
}
}
} else if len(rules) == 1 {
curRule = rules[0]
}
initializeInst = true
initialized = true
curGateway = addr
}
if !stat.HasInterfaces(hostNetName) {
logrus.WithFields(logrus.Fields{
"iface": hostNetName,
}).Info("hnetwork: Creating host interface")
err = create()
if err != nil {
return
}
curGateway = ""
initializeInst = true
}
hostBlock, err := block.GetNodeBlock(stat.Node().Id)
if err != nil {
return
}
gatewayCidr := hostBlock.GetGatewayCidr()
if gatewayCidr == "" {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
}).Error("hnetwork: Host network block gateway is invalid")
err = removeNetwork(stat)
if err != nil {
return
}
return
}
if curGateway != gatewayCidr {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
"host_block_gateway": gatewayCidr,
}).Info("hnetwork: Updating host network bridge")
err = setAddr(gatewayCidr)
if err != nil {
return
}
curGateway = gatewayCidr
initializeInst = true
}
if stat.Node().HostNat {
hostNet, e := hostBlock.GetNetwork()
if e != nil {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
"error": e,
}).Error("hnetwork: Host nat block network invalid")
} else {
newRule := &IptablesRule{
Source: hostNet.String(),
Output: stat.Node().DefaultInterface,
}
if curRule == nil || curRule.Source != newRule.Source ||
curRule.Output != newRule.Output {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
"host_source": newRule.Source,
"host_output": newRule.Output,
}).Info("hnetwork: Updating host network nat")
if curRule != nil {
err = curRule.Remove()
if err != nil {
return
}
curRule = nil
}
err = newRule.Add()
if err != nil {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
"host_source": newRule.Source,
"host_output": newRule.Output,
"error": err,
}).Error("hnetwork: Host nat add rule failed")
err = nil
} else {
curRule = newRule
}
initializeInst = true
}
}
} else if curRule != nil {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
"host_source": curRule.Source,
"host_output": curRule.Output,
}).Info("hnetwork: Updating host network nat")
err = curRule.Remove()
if err != nil {
return
}
curRule = nil
}
if initializeInst {
logrus.WithFields(logrus.Fields{
"host_block": hostBlock.Id.Hex(),
}).Info("hnetwork: Updating instance host network")
instances := stat.Instances()
for _, inst := range instances {
utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
},
"ip", "link", "set",
vm.GetIfaceHost(inst.Id, 0), "master", hostNetName,
)
}
}
return
}
================================================
FILE: hnetwork/utils.go
================================================
package hnetwork
import (
"fmt"
"github.com/pritunl/pritunl-cloud/bridges"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func create() (err error) {
err = iproute.BridgeAdd("", settings.Hypervisor.HostNetworkName)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link", "set",
"dev", settings.Hypervisor.HostNetworkName, "up",
)
if err != nil {
return
}
bridges.ClearCache()
return
}
func getAddr() (addr string, err error) {
address, _, err := iproute.AddressGetIface(
"", settings.Hypervisor.HostNetworkName)
if err != nil {
return
}
if address != nil {
addr = address.Local + fmt.Sprintf("/%d", address.Prefix)
}
return
}
func setAddr(addr string) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link", "set",
"dev", settings.Hypervisor.HostNetworkName, "up",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "addr", "flush",
"dev", settings.Hypervisor.HostNetworkName,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "addr", "add", addr,
"dev", settings.Hypervisor.HostNetworkName,
)
if err != nil {
return
}
return
}
func clearAddr() (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link", "set",
"dev", settings.Hypervisor.HostNetworkName, "up",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "addr", "flush",
"dev", settings.Hypervisor.HostNetworkName,
)
if err != nil {
return
}
return
}
================================================
FILE: hugepages/hugepages.go
================================================
package hugepages
import (
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
func HugepageSize() (count int, size uint64, err error) {
virt, err := utils.GetMemInfo()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "hugepages: Failed to read virtual memory"),
}
return
}
count = int(virt.HugePagesTotal)
size = virt.HugePageSize
if size < 1024 {
err = &errortypes.ReadError{
errors.Newf("hugepages: Invalid hugepage size %d", size),
}
return
}
return
}
func UpdateHugepagesSize() (err error) {
err = utils.ExistsMkdir(settings.Hypervisor.HugepagesPath, 0755)
if err != nil {
return
}
nodeHugepagesSize := node.Self.HugepagesSize
if nodeHugepagesSize == 0 {
return
}
curHugepagesCount, hugepageSize, err := HugepageSize()
if err != nil {
return
}
hugepagesSize := uint64(nodeHugepagesSize) * 1024
hugepagesCount := int(hugepagesSize / hugepageSize)
if curHugepagesCount != hugepagesCount {
logrus.WithFields(logrus.Fields{
"cur_nr_hugepages": curHugepagesCount,
"new_nr_hugepages": hugepagesCount,
}).Info("hugepages: Updating hugepages size")
_, err = utils.ExecCombinedOutputLogged(
nil,
"sysctl",
"-w",
fmt.Sprintf("vm.nr_hugepages=%d", hugepagesCount),
)
if err != nil {
return
}
}
return
}
================================================
FILE: image/constants.go
================================================
package image
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/tools/set"
)
const (
Uefi = "uefi"
Bios = "bios"
Unknown = "unknown"
Linux = "linux"
LinuxLegacy = "linux_legacy"
LinuxUnsigned = "linux_unsigned"
Bsd = "bsd"
RedHat = "redhat"
Fedora = "fedora"
Ubuntu = "ubuntu"
AlmaLinux8 = "almalinux8"
AlmaLinux9 = "almalinux9"
AlmaLinux10 = "almalinux10"
AlmaLinux11 = "almalinux11"
AlmaLinux12 = "almalinux12"
AlmaLinux13 = "almalinux13"
AlmaLinux14 = "almalinux14"
AlmaLinux15 = "almalinux15"
AlmaLinux16 = "almalinux16"
AlpineLinux = "alpinelinux"
ArchLinux = "archlinux"
Fedora42 = "fedora42"
Fedora43 = "fedora43"
Fedora44 = "fedora44"
Fedora45 = "fedora45"
Fedora46 = "fedora46"
Fedora47 = "fedora47"
Fedora48 = "fedora48"
Fedora49 = "fedora49"
Fedora50 = "fedora50"
Fedora51 = "fedora51"
Fedora52 = "fedora52"
Fedora53 = "fedora53"
Fedora54 = "fedora54"
Fedora55 = "fedora55"
Fedora56 = "fedora56"
Fedora57 = "fedora57"
Fedora58 = "fedora58"
Fedora59 = "fedora59"
Fedora60 = "fedora60"
Fedora61 = "fedora61"
Fedora62 = "fedora62"
FreeBSD = "freebsd"
OracleLinux7 = "oraclelinux7"
OracleLinux8 = "oraclelinux8"
OracleLinux9 = "oraclelinux9"
OracleLinux10 = "oraclelinux10"
OracleLinux11 = "oraclelinux11"
OracleLinux12 = "oraclelinux12"
OracleLinux13 = "oraclelinux13"
OracleLinux14 = "oraclelinux14"
OracleLinux15 = "oraclelinux15"
OracleLinux16 = "oraclelinux16"
RockyLinux8 = "rockylinux8"
RockyLinux9 = "rockylinux9"
RockyLinux10 = "rockylinux10"
RockyLinux11 = "rockylinux11"
RockyLinux12 = "rockylinux12"
RockyLinux13 = "rockylinux13"
RockyLinux14 = "rockylinux14"
RockyLinux15 = "rockylinux15"
RockyLinux16 = "rockylinux16"
Ubuntu2404 = "ubuntu2404"
Ubuntu2604 = "ubuntu2604"
Ubuntu2804 = "ubuntu2804"
Ubuntu3004 = "ubuntu3004"
Ubuntu3204 = "ubuntu3204"
Ubuntu3404 = "ubuntu3404"
Ubuntu3604 = "ubuntu3604"
Ubuntu3804 = "ubuntu3804"
Ubuntu4004 = "ubuntu4004"
Ubuntu4204 = "ubuntu4204"
Ubuntu4404 = "ubuntu4404"
)
var (
Global = bson.NilObjectID
Releases = set.NewSet(
AlmaLinux8,
AlmaLinux9,
AlmaLinux10,
AlmaLinux11,
AlmaLinux12,
AlmaLinux13,
AlmaLinux14,
AlmaLinux15,
AlmaLinux16,
AlpineLinux,
ArchLinux,
Fedora42,
Fedora43,
Fedora44,
Fedora45,
Fedora46,
Fedora47,
Fedora48,
Fedora49,
Fedora50,
Fedora51,
Fedora52,
Fedora53,
Fedora54,
Fedora55,
Fedora56,
Fedora57,
Fedora58,
Fedora59,
Fedora60,
Fedora61,
Fedora62,
FreeBSD,
OracleLinux7,
OracleLinux8,
OracleLinux9,
OracleLinux10,
OracleLinux11,
OracleLinux12,
OracleLinux13,
OracleLinux14,
OracleLinux15,
OracleLinux16,
RockyLinux8,
RockyLinux9,
RockyLinux10,
RockyLinux11,
RockyLinux12,
RockyLinux13,
RockyLinux14,
RockyLinux15,
RockyLinux16,
Ubuntu2404,
Ubuntu2604,
Ubuntu2804,
Ubuntu3004,
Ubuntu3204,
Ubuntu3404,
Ubuntu3604,
Ubuntu3804,
Ubuntu4004,
Ubuntu4204,
Ubuntu4404,
)
ValidSystemTypes = set.NewSet(
Linux,
LinuxLegacy,
LinuxUnsigned,
Bsd,
)
ValidSystemKinds = set.NewSet(
AlpineLinux,
ArchLinux,
RedHat,
Fedora,
Ubuntu,
FreeBSD,
)
)
================================================
FILE: image/errortypes.go
================================================
package image
import (
"github.com/dropbox/godropbox/errors"
)
type LostImageError struct {
errors.DropboxError
}
================================================
FILE: image/image.go
================================================
package image
import (
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Image struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Disk bson.ObjectID `bson:"disk" json:"disk"`
Name string `bson:"name" json:"name"`
Release string `bson:"release" json:"release"`
Build string `bson:"build" json:"build"`
Comment string `bson:"comment" json:"comment"`
Deployment bson.ObjectID `bson:"deployment" json:"deployment"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Signed bool `bson:"signed" json:"signed"`
Type string `bson:"type" json:"type"`
SystemType string `bson:"system_type" json:"system_type"`
SystemKind string `bson:"system_kind" json:"system_kind"`
Firmware string `bson:"firmware" json:"firmware"`
Storage bson.ObjectID `bson:"storage" json:"storage"`
Key string `bson:"key" json:"key"`
LastModified time.Time `bson:"last_modified" json:"last_modified"`
StorageClass string `bson:"storage_class" json:"storage_class"`
Hash string `bson:"hash" json:"hash"`
Etag string `bson:"etag" json:"etag"`
Tags []string `bson:"-" json:"tags"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Release string `bson:"release" json:"release"`
Build string `bson:"build" json:"build"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Deployment bson.ObjectID `bson:"deployment" json:"deployment"`
Type string `bson:"type" json:"type"`
Firmware string `bson:"firmware" json:"firmware"`
Key string `bson:"key" json:"key"`
Storage bson.ObjectID `bson:"storage" json:"storage"`
Tags []string `bson:"-" json:"tags"`
}
func (i *Image) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
i.Name = utils.FilterName(i.Name)
if i.Firmware == "" {
i.Firmware = Uefi
}
if i.SystemType == "" {
i.SystemType = Linux
}
if !ValidSystemTypes.Contains(i.SystemType) {
errData = &errortypes.ErrorData{
Error: "invalid_system_type",
Message: "Image system type invalid",
}
return
}
if i.SystemKind != "" && !ValidSystemKinds.Contains(i.SystemKind) {
errData = &errortypes.ErrorData{
Error: "invalid_system_kind",
Message: "Image system kind invalid",
}
return
}
return
}
func (i *Image) Parse() {
if i.Name == "" {
i.Name = i.Key
}
if i.Signed {
i.Name, i.Release, i.Build = ParseImageName(i.Key)
}
}
func (i *Image) GetSystemType() string {
if i.SystemType != "" {
return i.SystemType
}
name := strings.ToLower(i.Name)
if strings.Contains(name, "bsd") {
return Bsd
}
if strings.Contains(name, "alpinelinux") {
return LinuxUnsigned
}
if strings.Contains(name, "archlinux") {
return LinuxUnsigned
}
if strings.Contains(name, "oraclelinux7") {
return LinuxLegacy
}
if strings.Contains(name, "redhat7") {
return LinuxLegacy
}
return Linux
}
func (i *Image) GetSystemKind() string {
if i.SystemKind != "" {
return i.SystemKind
}
name := strings.ToLower(i.Name)
if strings.Contains(name, "freebsd") {
return FreeBSD
}
if strings.Contains(name, "alpinelinux") {
return AlpineLinux
}
if strings.Contains(name, "archlinux") {
return ArchLinux
}
if strings.Contains(name, "ubuntu") {
return Ubuntu
}
if strings.Contains(name, "fedora") {
return Fedora
}
if strings.Contains(name, "redhat") ||
strings.Contains(name, "almalinux") ||
strings.Contains(name, "oraclelinux") ||
strings.Contains(name, "rockylinux") {
return RedHat
}
return ""
}
func (i *Image) Json() {
i.Parse()
}
func (i *Image) Commit(db *database.Database) (err error) {
coll := db.Images()
err = coll.Commit(i.Id, i)
if err != nil {
return
}
return
}
func (i *Image) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Images()
err = coll.CommitFields(i.Id, i, fields)
if err != nil {
return
}
return
}
func (i *Image) Insert(db *database.Database) (err error) {
coll := db.Images()
_, err = coll.InsertOne(db, i)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (i *Image) Upsert(db *database.Database) (err error) {
coll := db.Images()
fields := bson.M{
"name": i.Name,
"deployment": i.Deployment,
"organization": i.Organization,
"disk": i.Disk,
"signed": i.Signed,
"type": i.Type,
"system_type": i.SystemType,
"system_kind": i.SystemKind,
"firmware": i.Firmware,
"storage": i.Storage,
"key": i.Key,
"last_modified": i.LastModified,
"storage_class": i.StorageClass,
"hash": i.Hash,
"etag": i.Etag,
}
resp, err := coll.UpdateOne(
db,
&bson.M{
"storage": i.Storage,
"key": i.Key,
},
&bson.M{
"$set": fields,
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
err = database.ParseError(err)
return
}
if resp.UpsertedID != nil {
i.Id = resp.UpsertedID.(bson.ObjectID)
}
return
}
func (i *Image) Sync(db *database.Database) (err error) {
coll := db.Images()
i.Parse()
i.SystemType = i.GetSystemType()
i.SystemKind = i.GetSystemKind()
if strings.HasPrefix(i.Key, "backup/") ||
strings.HasPrefix(i.Key, "snapshot/") {
resp, e := coll.UpdateOne(
db,
&bson.M{
"storage": i.Storage,
"key": i.Key,
},
&bson.M{
"$set": &bson.M{
"organization": bson.NilObjectID,
"release": i.Release,
"build": i.Build,
"storage": i.Storage,
"key": i.Key,
"signed": i.Signed,
"type": i.Type,
"system_type": i.SystemType,
"system_kind": i.SystemKind,
"firmware": i.Firmware,
"etag": i.Etag,
"last_modified": i.LastModified,
"storage_class": i.StorageClass,
},
"$setOnInsert": &bson.M{
"name": i.Name,
"disk": bson.NilObjectID,
"deployment": bson.NilObjectID,
},
},
)
if e != nil {
err = database.ParseError(e)
if _, ok := err.(*database.NotFoundError); ok {
err = &LostImageError{
errors.Wrap(err, "image: Lost image"),
}
}
return
}
if resp.UpsertedID != nil {
i.Id = resp.UpsertedID.(bson.ObjectID)
}
} else {
resp, e := coll.UpdateOne(
db,
&bson.M{
"storage": i.Storage,
"key": i.Key,
},
&bson.M{
"$set": &bson.M{
"organization": bson.NilObjectID,
"name": i.Name,
"release": i.Release,
"build": i.Build,
"storage": i.Storage,
"key": i.Key,
"signed": i.Signed,
"type": i.Type,
"system_type": i.SystemType,
"system_kind": i.SystemKind,
"firmware": i.Firmware,
"etag": i.Etag,
"last_modified": i.LastModified,
},
"$setOnInsert": &bson.M{
"disk": bson.NilObjectID,
"deployment": bson.NilObjectID,
},
},
options.UpdateOne().SetUpsert(true),
)
if e != nil {
err = database.ParseError(e)
return
}
if resp.UpsertedID != nil {
i.Id = resp.UpsertedID.(bson.ObjectID)
}
}
return
}
func (i *Image) Remove(db *database.Database) (err error) {
if !i.Deployment.IsZero() {
err = deployment.Remove(db, i.Deployment)
if err != nil {
return
}
}
err = Remove(db, i.Id)
if err != nil {
return
}
return
}
================================================
FILE: image/sort.go
================================================
package image
import (
"github.com/pritunl/pritunl-cloud/utils"
)
type ImagesSort []*Image
func (x ImagesSort) Len() int {
return len(x)
}
func (x ImagesSort) Swap(i, j int) {
x[i], x[j] = x[j], x[i]
}
func (x ImagesSort) Less(i, j int) bool {
return utils.NaturalCompare(x[i].Name, x[j].Name) < 0
}
type CompletionsSort []*Completion
func (x CompletionsSort) Len() int {
return len(x)
}
func (x CompletionsSort) Swap(i, j int) {
x[i], x[j] = x[j], x[i]
}
func (x CompletionsSort) Less(i, j int) bool {
return utils.NaturalCompare(x[i].Name, x[j].Name) < 0
}
================================================
FILE: image/utils.go
================================================
package image
import (
"crypto/md5"
"fmt"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
minio "github.com/minio/minio-go/v7"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
etagReg = regexp.MustCompile("[^a-zA-Z0-9]+")
distroRe = regexp.MustCompile(`^([a-z]+)([0-9]*)`)
dateRe = regexp.MustCompile(`_(\d{2})(\d{2})(\d{2})?$`)
)
func GetEtag(info minio.ObjectInfo) string {
etag := info.ETag
if etag == "" {
modifiedHash := md5.New()
modifiedHash.Write(
[]byte(info.LastModified.Format(time.RFC3339)))
etag = fmt.Sprintf("%x", modifiedHash.Sum(nil))
}
return etagReg.ReplaceAllString(etag, "")
}
func ParseImageName(key string) (name, release, build string) {
baseName := strings.TrimSuffix(key, filepath.Ext(key))
dateMatch := dateRe.FindStringSubmatch(baseName)
if len(dateMatch) != 3 && len(dateMatch) != 4 {
name = key
return
}
yearStr, monthStr := dateMatch[1], dateMatch[2]
build = yearStr + monthStr
if len(dateMatch) == 4 {
build += dateMatch[3]
}
base := strings.TrimSuffix(baseName, dateMatch[0])
tokens := strings.Split(base, "_")
if len(tokens) == 0 {
name = key
return
}
distroMatch := distroRe.FindStringSubmatch(tokens[0])
if len(distroMatch) < 2 {
name = key
return
}
distro := distroMatch[1]
version := ""
if len(distroMatch) >= 3 {
version = distroMatch[2]
}
if version == "" {
name = fmt.Sprintf("%s-%s%s", distro, yearStr, monthStr)
} else {
name = fmt.Sprintf("%s%s-%s%s", distro, version, yearStr, monthStr)
}
if Releases.Contains(distro + version) {
release = distro + version
}
return
}
func Get(db *database.Database, imgId bson.ObjectID) (
img *Image, err error) {
coll := db.Images()
img = &Image{}
err = coll.FindOneId(imgId, img)
if err != nil {
return
}
return
}
func GetKey(db *database.Database, storeId bson.ObjectID, key string) (
img *Image, err error) {
coll := db.Images()
img = &Image{}
err = coll.FindOne(db, &bson.M{
"storage": storeId,
"key": key,
}).Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrg(db *database.Database, orgId, imgId bson.ObjectID) (
img *Image, err error) {
coll := db.Images()
img = &Image{}
err = coll.FindOne(db, &bson.M{
"_id": imgId,
"organization": orgId,
}).Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrgPublic(db *database.Database, orgId, imgId bson.ObjectID) (
img *Image, err error) {
coll := db.Images()
img = &Image{}
err = coll.FindOne(db, &bson.M{
"_id": imgId,
"organization": Global,
}).Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (img *Image, err error) {
coll := db.Images()
img = &Image{}
err = coll.FindOne(db, query).Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Distinct(db *database.Database, storeId bson.ObjectID) (
keys []string, err error) {
coll := db.Images()
keys = []string{}
err = coll.Distinct(db, "key", &bson.M{
"storage": storeId,
}).Decode(&keys)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func ExistsOrg(db *database.Database, orgId, imgId bson.ObjectID) (
exists bool, err error) {
coll := db.Images()
n, err := coll.CountDocuments(db, &bson.M{
"_id": imgId,
"organization": Global,
})
if err != nil {
err = database.ParseError(err)
return
}
if n > 0 {
exists = true
}
return
}
func GetAll(db *database.Database, query *bson.M) (
imgs []*Image, err error) {
coll := db.Images()
imgs = []*Image{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
img := &Image{}
err = cursor.Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
imgs = append(imgs, img)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllCompletion(db *database.Database, query *bson.M) (
imgs []*Completion, err error) {
coll := db.Images()
imgs = []*Completion{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetProjection(bson.D{
{"_id", 1},
{"name", 1},
{"release", 1},
{"build", 1},
{"organization", 1},
{"deployment", 1},
{"type", 1},
{"firmware", 1},
{"key", 1},
{"storage", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
img := &Completion{}
err = cursor.Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
imgs = append(imgs, img)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M, page, pageCount int64) (
imgs []*Image, count int64, err error) {
coll := db.Images()
imgs = []*Image{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"key", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
defer cursor.Close(db)
for cursor.Next(db) {
img := &Image{}
err = cursor.Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
imgs = append(imgs, img)
img = &Image{}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNames(db *database.Database, query *bson.M) (
images []*Image, err error) {
coll := db.Images()
images = []*Image{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"key", 1}}).
SetProjection(bson.D{
{"name", 1},
{"key", 1},
{"signed", 1},
{"firmware", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
img := &Image{}
err = cursor.Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
img.Json()
images = append(images, img)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllKeys(db *database.Database) (keys set.Set, err error) {
coll := db.Images()
keys = set.NewSet()
cursor, err := coll.Find(
db,
&bson.M{},
options.Find().
SetSort(bson.D{{"key", 1}}).
SetProjection(bson.D{
{"_id", 1},
{"etag", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
img := &Image{}
err = cursor.Decode(img)
if err != nil {
err = database.ParseError(err)
return
}
keys.Add(fmt.Sprintf("%s-%s", img.Id.Hex(), img.Etag))
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, imgId bson.ObjectID) (err error) {
coll := db.Images()
_, err = coll.DeleteOne(db, &bson.M{
"_id": imgId,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
return
}
================================================
FILE: imds/config.go
================================================
package imds
import (
"sync"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
)
var (
curConfs = map[bson.ObjectID]*types.Config{}
curConfsLock = sync.Mutex{}
)
func BuildConfig(inst *instance.Instance, virt *vm.VirtualMachine,
unt *unit.Unit, spc *spec.Spec, vc *vpc.Vpc, subnet *vpc.Subnet,
pods []*pod.Pod, podUnitsMap map[bson.ObjectID][]*unit.Unit,
deployments map[bson.ObjectID]*deployment.Deployment,
secrs []*secret.Secret, certs []*certificate.Certificate,
domains []*types.Domain) (conf *types.Config, err error) {
conf = &types.Config{
ImdsHostSecret: virt.ImdsHostSecret,
ClientIps: inst.PrivateIps,
Node: types.NewNode(node.Self),
Instance: types.NewInstance(inst),
Vpc: types.NewVpc(vc),
Subnet: types.NewSubnet(subnet),
Pods: types.NewPods(pods, podUnitsMap, deployments),
Secrets: types.NewSecrets(secrs),
Certificates: types.NewCertificates(certs),
Domains: domains,
}
if spc != nil {
conf.Spec = spc.Id
conf.SpecData = spc.Data
conf.Journals = types.NewJournals(spc)
}
return
}
func SetConfigs(cnfs map[bson.ObjectID]*types.Config) {
curConfsLock.Lock()
curConfs = cnfs
curConfsLock.Unlock()
}
func GetConfigs() (
cnfs map[bson.ObjectID]*types.Config) {
curConfsLock.Lock()
cnfs = curConfs
curConfsLock.Unlock()
return
}
================================================
FILE: imds/imds.go
================================================
package imds
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"strings"
"sync"
"sync/atomic"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/advisory"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/imds/server/utils"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/journal"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/telemetry"
pritunlutils "github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/tools/errors"
"github.com/sirupsen/logrus"
)
var (
hashes = map[bson.ObjectID]uint32{}
hashesLock = sync.Mutex{}
counter = atomic.Uint64{}
)
const (
counterMax = 2000000000
)
func mergeUpdateDetails(db *database.Database, instId bson.ObjectID,
updates []*telemetry.Update) (err error) {
if len(updates) == 0 {
return
}
coll := db.Instances()
inst := &instance.Instance{}
err = coll.FindOne(db, &bson.M{
"_id": instId,
}, database.FindOneProject("guest.updates")).Decode(inst)
if err != nil {
err = database.ParseError(err)
err = database.IgnoreNotFoundError(err)
return
}
if inst.Guest == nil {
return
}
detailsMap := map[string][]*advisory.Advisory{}
for _, upd := range inst.Guest.Updates {
if upd.Advisory != "" && len(upd.Details) > 0 {
detailsMap[upd.Advisory] = upd.Details
}
}
for _, upd := range updates {
if details, ok := detailsMap[upd.Advisory]; ok {
upd.Details = details
}
}
return
}
func Sync(db *database.Database, namespace string,
instId, deplyId bson.ObjectID, conf *types.Config) (err error) {
sockPath := paths.GetImdsSockPath(instId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if !exists {
return
}
client := &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context,
_, _ string) (net.Conn, error) {
return net.Dial("unix", sockPath)
},
},
Timeout: 6 * time.Second,
}
var body io.Reader
hashesLock.Lock()
curHash := hashes[instId]
hashesLock.Unlock()
if conf != nil && curHash != conf.Hash {
reqDataBuf := &bytes.Buffer{}
err = json.NewEncoder(reqDataBuf).Encode(conf)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "agent: Failed to parse request data"),
}
return
}
body = reqDataBuf
}
req, err := http.NewRequest("PUT", "http://unix/sync", body)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to create imds request"),
}
return
}
req.Header.Set("User-Agent", "pritunl-imds")
req.Header.Set("Auth-Token", conf.ImdsHostSecret)
if body != nil {
req.Header.Set("Content-Type", "application/json")
}
resp, e := client.Do(req)
if e != nil {
err = &errortypes.RequestError{
errors.Wrap(e, "agent: Imds request failed"),
}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body := ""
data, _ := ioutil.ReadAll(resp.Body)
if data != nil {
body = string(data)
}
errData := &errortypes.ErrorData{}
err = json.Unmarshal(data, errData)
if err != nil || errData.Error == "" {
errData = nil
}
if errData != nil && errData.Message != "" {
body = errData.Message
}
err = &errortypes.RequestError{
errors.Newf(
"agent: Imds host sync error %d - %s",
resp.StatusCode, body),
}
return
}
ste := &types.State{}
err = json.NewDecoder(resp.Body).Decode(ste)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to decode imds host sync resp"),
}
return
}
hashesLock.Lock()
hashes[instId] = ste.Hash
hashesLock.Unlock()
if ste.Status != "" {
coll := db.Instances()
data := bson.M{
"guest.status": ste.Status,
"guest.timestamp": time.Now(),
"guest.heartbeat": ste.Timestamp,
"guest.memory": ste.Memory,
"guest.hugepages": ste.HugePages,
"guest.load1": ste.Load1,
"guest.load5": ste.Load5,
"guest.load15": ste.Load15,
}
if ste.DhcpIp != nil {
data["dhcp_ip"] = ste.DhcpIp.String()
}
if ste.DhcpIp6 != nil {
data["dhcp_ip6"] = ste.DhcpIp6.String()
}
if ste.Updates != nil {
err = mergeUpdateDetails(db, instId, ste.Updates)
if err != nil {
return
}
data["guest.updates"] = ste.Updates
}
_, err = coll.UpdateOne(db, &bson.M{
"_id": instId,
}, bson.M{
"$set": data,
})
if err != nil {
err = database.ParseError(err)
return
}
var kind int32
var resource bson.ObjectID
if !deplyId.IsZero() {
kind = journal.DeploymentAgent
resource = deplyId
} else {
kind = journal.InstanceAgent
resource = instId
}
for _, entry := range ste.Output {
if entry.Level < 1 || entry.Level > 9 {
continue
}
if len(entry.Message) > 100000 {
entry.Message = entry.Message[:100000]
}
jrnl := &journal.Journal{
Resource: resource,
Kind: kind,
Level: entry.Level,
Timestamp: entry.Timestamp,
Count: int32(counter.Add(1) % counterMax),
Message: entry.Message,
}
err = jrnl.Insert(db)
if err != nil {
return
}
}
if ste.Journals != nil {
indexes := map[string]int32{}
for _, jrnl := range conf.Journals {
indexes[jrnl.Key] = jrnl.Index
}
for key, output := range ste.Journals {
index := indexes[key]
if index == 0 {
continue
}
for _, entry := range output {
if entry.Level < 1 || entry.Level > 9 {
continue
}
if len(entry.Message) > 100000 {
entry.Message = entry.Message[:100000]
}
jrnl := &journal.Journal{
Resource: resource,
Kind: index,
Level: entry.Level,
Timestamp: entry.Timestamp,
Count: int32(counter.Add(1) % counterMax),
Message: entry.Message,
}
err = jrnl.Insert(db)
if err != nil {
return
}
}
}
}
curIp := ""
curIp6 := ""
curIpPrefix := ""
curIpPrefix6 := ""
curIpCached := false
curIpCached6 := false
newIp := ""
newIp6 := ""
newIpPrefix := ""
newIpPrefix6 := ""
clearIpCache := false
if ste.DhcpIp != nil {
addrStore, ok := store.GetAddress(instId)
if ok {
curIpCached = true
curIp = addrStore.Addr
if ste.DhcpIface == ste.DhcpIface6 {
curIpCached6 = true
curIp6 = addrStore.Addr6
}
} else {
address, address6, e := iproute.AddressGetIfaceMod(
namespace, ste.DhcpIface)
if e == nil && address != nil {
curIpCached = false
curIp = address.Local
curIpPrefix = fmt.Sprintf(
"%s/%d", address.Local, address.Prefix)
if ste.DhcpIface == ste.DhcpIface6 && address6 != nil {
curIpCached6 = false
curIp6 = address6.Local
curIpPrefix6 = fmt.Sprintf(
"%s/%d", address6.Local, address6.Prefix)
}
}
}
newIpPrefix = ste.DhcpIp.String()
newIp = strings.Split(newIpPrefix, "/")[0]
}
if ste.DhcpIp6 != nil {
if curIp6 == "" {
addrStore, ok := store.GetAddress(instId)
if ok {
curIpCached6 = true
curIp6 = addrStore.Addr6
} else {
_, address6, e := iproute.AddressGetIfaceMod(
namespace, ste.DhcpIface6)
if e == nil && address6 != nil {
curIpCached6 = false
curIp6 = address6.Local
curIpPrefix6 = fmt.Sprintf(
"%s/%d", address6.Local, address6.Prefix)
}
}
}
newIpPrefix6 = ste.DhcpIp6.String()
newIp6 = strings.Split(newIpPrefix6, "/")[0]
}
if newIp != "" && newIp != curIp {
if curIpCached {
address, address6, e := iproute.AddressGetIfaceMod(
namespace, ste.DhcpIface)
if e == nil && address != nil {
curIpCached = false
curIp = address.Local
curIpPrefix = fmt.Sprintf(
"%s/%d", address.Local, address.Prefix)
if ste.DhcpIface == ste.DhcpIface6 && address6 != nil {
curIpCached6 = false
curIp6 = address6.Local
curIpPrefix6 = fmt.Sprintf(
"%s/%d", address6.Local, address6.Prefix)
}
}
}
if newIp != curIp {
logrus.WithFields(logrus.Fields{
"instance": instId.Hex(),
"namespace": namespace,
"cur_ip": curIpPrefix,
"new_ip": newIpPrefix,
}).Info("imds: Updating instance DHCP IPv4 address")
if curIpPrefix != "" {
_, err = pritunlutils.ExecCombinedOutputLogged(
[]string{"File exists", "Cannot assign"},
"ip", "netns", "exec", namespace,
"ip", "addr",
"del", curIpPrefix,
"dev", ste.DhcpIface,
)
if err != nil {
return
}
}
_, err = pritunlutils.ExecCombinedOutputLogged(
[]string{"File exists", "already assigned"},
"ip", "netns", "exec", namespace,
"ip", "addr",
"add", newIpPrefix,
"dev", ste.DhcpIface,
)
if err != nil {
return
}
if ste.DhcpGateway != nil {
_, err = pritunlutils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", namespace,
"ip", "route",
"add", "default",
"via", ste.DhcpGateway.String(),
"dev", ste.DhcpIface,
)
if err != nil {
return
}
}
clearIpCache = true
}
}
if newIp6 != "" && newIp6 != curIp6 {
if curIpCached6 {
_, address6, e := iproute.AddressGetIfaceMod(
namespace, ste.DhcpIface6)
if e == nil && address6 != nil {
curIpCached6 = false
curIp6 = address6.Local
curIpPrefix6 = fmt.Sprintf(
"%s/%d", address6.Local, address6.Prefix)
}
}
if newIp6 != curIp6 {
logrus.WithFields(logrus.Fields{
"instance": instId.Hex(),
"namespace": namespace,
"cur_ip6": curIpPrefix6,
"new_ip6": newIpPrefix6,
}).Info("imds: Updating instance DHCP IPv6 address")
if curIpPrefix6 != "" {
_, err = pritunlutils.ExecCombinedOutputLogged(
[]string{"File exists", "Cannot assign"},
"ip", "netns", "exec", namespace,
"ip", "addr",
"del", curIpPrefix6,
"dev", ste.DhcpIface6,
)
if err != nil {
return
}
}
_, err = pritunlutils.ExecCombinedOutputLogged(
[]string{"File exists", "already assigned"},
"ip", "netns", "exec", namespace,
"ip", "addr",
"add", newIpPrefix6,
"dev", ste.DhcpIface6,
)
if err != nil {
return
}
clearIpCache = true
}
}
if clearIpCache {
store.RemAddress(instId)
}
}
return
}
func Pull(db *database.Database, instId, deplyId bson.ObjectID,
imdsHostSecret string, journals []*types.Journal) (err error) {
sockPath := paths.GetImdsSockPath(instId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if !exists {
return
}
client := &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context,
_, _ string) (net.Conn, error) {
return net.Dial("unix", sockPath)
},
},
Timeout: 6 * time.Second,
}
req, err := http.NewRequest("GET", "http://unix/sync", nil)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to create imds request"),
}
return
}
req.Header.Set("User-Agent", "pritunl-imds")
req.Header.Set("Auth-Token", imdsHostSecret)
resp, e := client.Do(req)
if e != nil {
err = &errortypes.RequestError{
errors.Wrap(e, "agent: Imds request failed"),
}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body := ""
data, _ := ioutil.ReadAll(resp.Body)
if data != nil {
body = string(data)
}
errData := &errortypes.ErrorData{}
err = json.Unmarshal(data, errData)
if err != nil || errData.Error == "" {
errData = nil
}
if errData != nil && errData.Message != "" {
body = errData.Message
}
err = &errortypes.RequestError{
errors.Newf(
"agent: Imds host sync error %d - %s",
resp.StatusCode, body),
}
return
}
ste := &types.State{}
err = json.NewDecoder(resp.Body).Decode(ste)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to decode imds host sync resp"),
}
return
}
if ste.Status != "" {
coll := db.Instances()
data := bson.M{
"guest.status": ste.Status,
"guest.timestamp": time.Now(),
"guest.heartbeat": ste.Timestamp,
"guest.memory": ste.Memory,
"guest.hugepages": ste.HugePages,
"guest.load1": ste.Load1,
"guest.load5": ste.Load5,
"guest.load15": ste.Load15,
}
if ste.DhcpIp != nil {
data["dhcp_ip"] = ste.DhcpIp.String()
}
if ste.DhcpIp6 != nil {
data["dhcp_ip6"] = ste.DhcpIp6.String()
}
if ste.Updates != nil {
err = mergeUpdateDetails(db, instId, ste.Updates)
if err != nil {
return
}
data["guest.updates"] = ste.Updates
}
_, err = coll.UpdateOne(db, &bson.M{
"_id": instId,
}, bson.M{
"$set": data,
})
if err != nil {
err = database.ParseError(err)
return
}
var kind int32
var resource bson.ObjectID
if !deplyId.IsZero() {
kind = journal.DeploymentAgent
resource = deplyId
} else {
kind = journal.InstanceAgent
resource = instId
}
for _, entry := range ste.Output {
if entry.Level < 1 || entry.Level > 9 {
continue
}
if len(entry.Message) > 100000 {
entry.Message = entry.Message[:100000]
}
jrnl := &journal.Journal{
Resource: resource,
Kind: kind,
Level: entry.Level,
Timestamp: entry.Timestamp,
Count: int32(counter.Add(1) % counterMax),
Message: entry.Message,
}
err = jrnl.Insert(db)
if err != nil {
return
}
}
if ste.Journals != nil {
indexes := map[string]int32{}
for _, jrnl := range journals {
indexes[jrnl.Key] = jrnl.Index
}
for key, output := range ste.Journals {
index := indexes[key]
if index == 0 {
continue
}
for _, entry := range output {
if entry.Level < 1 || entry.Level > 9 {
continue
}
if len(entry.Message) > 100000 {
entry.Message = entry.Message[:100000]
}
jrnl := &journal.Journal{
Resource: resource,
Kind: index,
Level: entry.Level,
Timestamp: entry.Timestamp,
Count: int32(counter.Add(1) % counterMax),
Message: entry.Message,
}
err = jrnl.Insert(db)
if err != nil {
return
}
}
}
}
}
return
}
func State(db *database.Database, instId bson.ObjectID,
imdsHostSecret string) (ste *types.State, err error) {
sockPath := paths.GetImdsSockPath(instId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if !exists {
return
}
client := &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context,
_, _ string) (net.Conn, error) {
return net.Dial("unix", sockPath)
},
},
Timeout: 6 * time.Second,
}
req, err := http.NewRequest("GET", "http://unix/state", nil)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to create imds request"),
}
return
}
req.Header.Set("User-Agent", "pritunl-imds")
req.Header.Set("Auth-Token", imdsHostSecret)
resp, e := client.Do(req)
if e != nil {
err = &errortypes.RequestError{
errors.Wrap(e, "agent: Imds request failed"),
}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
body := ""
data, _ := ioutil.ReadAll(resp.Body)
if data != nil {
body = string(data)
}
errData := &errortypes.ErrorData{}
err = json.Unmarshal(data, errData)
if err != nil || errData.Error == "" {
errData = nil
}
if errData != nil && errData.Message != "" {
body = errData.Message
}
err = &errortypes.RequestError{
errors.Newf(
"agent: Imds host sync error %d - %s",
resp.StatusCode, body),
}
return
}
ste = &types.State{}
err = json.NewDecoder(resp.Body).Decode(ste)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "agent: Failed to decode imds host sync resp"),
}
return
}
return
}
================================================
FILE: imds/resource/resource.go
================================================
package resource
import (
"strings"
"github.com/pritunl/pritunl-cloud/finder"
"github.com/pritunl/pritunl-cloud/imds/server/config"
"github.com/pritunl/pritunl-cloud/secret"
)
func Query(resrc string, keys ...string) (val string, err error) {
var resrcInf interface{}
key := ""
isJson := false
switch resrc {
case finder.NodeKind:
if len(keys) != 2 || keys[0] != "self" {
break
}
key = keys[1]
resrcInf = config.Config.Node
break
case finder.InstanceKind:
if len(keys) != 2 || keys[0] != "self" {
break
}
key = keys[1]
resrcInf = config.Config.Instance
break
case finder.VpcKind:
if len(keys) != 2 || keys[0] != "self" {
break
}
key = keys[1]
resrcInf = config.Config.Vpc
break
case finder.SubnetKind:
if len(keys) != 2 || keys[0] != "self" {
break
}
key = keys[1]
resrcInf = config.Config.Subnet
break
case finder.SecretKind:
if len(keys) != 2 {
break
}
key = keys[1]
for _, secr := range config.Config.Secrets {
if secr.Name == keys[0] {
resrcInf = secr
if secr.Type == secret.Json &&
strings.HasPrefix(key, "data.") {
isJson = true
}
break
}
}
break
case finder.CertificateKind:
if len(keys) != 2 {
break
}
key = keys[1]
for _, cert := range config.Config.Certificates {
if cert.Name == keys[0] {
resrcInf = cert
break
}
}
break
case finder.PodKind:
if len(keys) == 2 {
key = keys[1]
} else if len(keys) == 4 {
key = keys[3]
} else {
break
}
for _, pd := range config.Config.Pods {
if pd.Name == keys[0] {
if len(keys) == 4 {
if keys[1] == finder.UnitKind {
for _, unit := range pd.Units {
if unit.Name == keys[2] {
resrcInf = unit
break
}
}
}
} else {
resrcInf = pd
}
break
}
}
break
case finder.UnitKind:
if len(keys) != 2 {
break
}
key = keys[1]
for _, pd := range config.Config.Pods {
for _, unit := range pd.Units {
if unit.Name == keys[0] {
resrcInf = unit
break
}
}
}
break
default:
return
}
if resrcInf == nil {
return
}
val, err = selector(resrcInf, key, isJson)
if err != nil {
return
}
return
}
================================================
FILE: imds/resource/utils.go
================================================
package resource
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func selector(v interface{}, key string, isJson bool) (val string, err error) {
valRef := reflect.ValueOf(v)
if valRef.Kind() != reflect.Ptr || valRef.IsNil() {
err = &errortypes.ParseError{
errors.New("Selector input invalid"),
}
return
}
elm := valRef.Elem()
if elm.Kind() != reflect.Struct {
err = &errortypes.ParseError{
errors.New("Selector kind invalid"),
}
return
}
jsonKey := ""
if isJson {
keys := strings.SplitN(key, ".", 2)
if len(keys) == 2 {
key = keys[0]
jsonKey = keys[1]
} else {
isJson = false
}
}
typ := elm.Type()
for i := 0; i < elm.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == key {
fieldVal := elm.Field(i)
if fieldVal.Kind() == reflect.Slice {
var elements []string
for j := 0; j < fieldVal.Len(); j++ {
elements = append(elements,
selectString(fieldVal.Index(j).Interface()))
}
val = strings.Join(elements, ",")
} else {
val = selectString(elm.Field(i).Interface())
if isJson && jsonKey != "" {
var jsonData map[string]any
err = json.Unmarshal([]byte(val), &jsonData)
if err != nil {
val = ""
return
}
jsonValue, exists := jsonData[jsonKey]
if !exists {
val = ""
return
}
val = jsonValString(jsonValue)
}
}
return
}
}
return
}
func jsonValString(value any) string {
switch val := value.(type) {
case string:
return val
case bool:
return strconv.FormatBool(val)
case float64:
return strconv.FormatFloat(val, 'f', -1, 64)
case int:
return strconv.Itoa(val)
case int64:
return strconv.FormatInt(val, 10)
case nil:
return ""
default:
jsonBytes, _ := json.Marshal(val)
return string(jsonBytes)
}
}
func selectString(obj interface{}) string {
if oid, ok := obj.(bson.ObjectID); ok {
return oid.Hex()
}
val := reflect.ValueOf(obj)
val = reflect.Indirect(val)
method := val.MethodByName("String")
if method.IsValid() &&
method.Type().NumIn() == 0 &&
method.Type().NumOut() == 1 &&
method.Type().Out(0).Kind() == reflect.String {
result := method.Call(nil)
return result[0].String()
}
return fmt.Sprintf("%v", obj)
}
================================================
FILE: imds/server/config/config.go
================================================
package config
import (
"github.com/pritunl/pritunl-cloud/imds/types"
)
var (
Config = &types.Config{}
)
================================================
FILE: imds/server/constants/constants.go
================================================
package constants
import (
"time"
)
const (
Version = "1.0.3229.20"
ConfRefresh = 500 * time.Millisecond
)
var (
Sock = ""
Host = "127.0.0.1"
Port = 80
Client = "127.0.0.1"
ClientSecret = ""
DhcpSecret = ""
HostSecret = ""
Interrupt = false
)
================================================
FILE: imds/server/errortypes/errortypes.go
================================================
package errortypes
import (
"github.com/dropbox/godropbox/errors"
)
type UnknownError struct {
errors.DropboxError
}
type NotFoundError struct {
errors.DropboxError
}
type ReadError struct {
errors.DropboxError
}
type WriteError struct {
errors.DropboxError
}
type ParseError struct {
errors.DropboxError
}
type AuthenticationError struct {
errors.DropboxError
}
type VerificationError struct {
errors.DropboxError
}
type ApiError struct {
errors.DropboxError
}
type DatabaseError struct {
errors.DropboxError
}
type RequestError struct {
errors.DropboxError
}
type ConnectionError struct {
errors.DropboxError
}
type TimeoutError struct {
errors.DropboxError
}
type ExecError struct {
errors.DropboxError
}
type NetworkError struct {
errors.DropboxError
}
type TypeError struct {
errors.DropboxError
}
type ErrorData struct {
Error string `json:"error"`
Message string `json:"error_msg"`
}
================================================
FILE: imds/server/handlers/certificate.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/config"
)
func certificatesGet(c *gin.Context) {
c.JSON(200, config.Config.Certificates)
}
================================================
FILE: imds/server/handlers/dhcp.go
================================================
package handlers
import (
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/dhcpc"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
"github.com/pritunl/pritunl-cloud/imds/server/state"
"github.com/pritunl/pritunl-cloud/utils"
)
func dhcpPut(c *gin.Context) {
data := &dhcpc.Lease{}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
state.Global.State.DhcpIface = data.Iface
state.Global.State.DhcpIface6 = data.Iface6
state.Global.State.DhcpIp = data.Address
state.Global.State.DhcpGateway = data.Gateway
state.Global.State.DhcpIp6 = data.Address6
c.JSON(200, map[string]string{})
}
================================================
FILE: imds/server/handlers/handlers.go
================================================
package handlers
import (
"crypto/subtle"
"net/http"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/constants"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
"github.com/pritunl/tools/logger"
)
type AuthenticationError struct {
Error string `json:"error"`
Message string `json:"message"`
}
func Recovery(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
err := &errortypes.UnknownError{
errors.Newf("handlers: Handler panic %s", r),
}
logger.WithFields(logger.Fields{
"error": err,
}).Error("handlers: Handler panic")
c.Writer.WriteHeader(http.StatusInternalServerError)
}
}()
c.Next()
}
func Errors(c *gin.Context) {
c.Next()
for _, err := range c.Errors {
logger.WithFields(logger.Fields{
"error": err,
}).Error("handlers: Handler error")
}
}
func AuthVirt(c *gin.Context) {
token := c.Request.Header.Get("Auth-Token")
if token == "" {
token = c.Query("token")
}
// TODO config.Config.ClientIps not loaded
// addr := utils.StripPort(c.Request.RemoteAddr)
// if len(config.Config.ClientIps) != 0 && config.Config.ClientIps[0] == "" &&
// !utils.StringsContains(config.Config.ClientIps, addr) {
// c.AbortWithStatusJSON(401, &AuthenticationError{
// Error: "authentication",
// Message: "Source IP address invalid",
// })
// return
// }
if c.Request.Header.Get("Origin") != "" ||
c.Request.Header.Get("Referer") != "" ||
c.Request.Header.Get("User-Agent") != "pritunl-imds" ||
constants.ClientSecret == "" ||
(subtle.ConstantTimeCompare([]byte(token),
[]byte(constants.ClientSecret)) != 1) {
c.AbortWithStatus(401)
return
}
c.Next()
}
func AuthDhcp(c *gin.Context) {
token := c.Request.Header.Get("Auth-Token")
if token == "" {
token = c.Query("token")
}
if c.Request.Header.Get("Origin") != "" ||
c.Request.Header.Get("Referer") != "" ||
c.Request.Header.Get("User-Agent") != "pritunl-dhcp" ||
constants.DhcpSecret == "" ||
(subtle.ConstantTimeCompare([]byte(token),
[]byte(constants.DhcpSecret)) != 1) {
c.AbortWithStatus(401)
return
}
c.Next()
}
func AuthHost(c *gin.Context) {
token := c.Request.Header.Get("Auth-Token")
if token == "" {
token = c.Query("token")
}
if c.Request.Header.Get("Origin") != "" ||
c.Request.Header.Get("Referer") != "" ||
c.Request.Header.Get("User-Agent") != "pritunl-imds" ||
constants.HostSecret == "" ||
(subtle.ConstantTimeCompare([]byte(token),
[]byte(constants.HostSecret)) != 1) {
c.AbortWithStatus(401)
return
}
c.Next()
}
func RegisterVirt(engine *gin.Engine) {
engine.Use(Recovery)
engine.Use(Errors)
virtGroup := engine.Group("")
virtGroup.Use(AuthVirt)
dhcpGroup := engine.Group("")
dhcpGroup.Use(AuthDhcp)
virtGroup.GET("/query/:resource", queryGet)
virtGroup.GET("/query/:resource/:key1", queryGet)
virtGroup.GET("/query/:resource/:key1/:key2", queryGet)
virtGroup.GET("/query/:resource/:key1/:key2/:key3", queryGet)
virtGroup.GET("/query/:resource/:key1/:key2/:key3/:key4", queryGet)
virtGroup.GET("/node", nodeGet)
virtGroup.GET("/instance", instanceGet)
virtGroup.GET("/vpc", vpcGet)
virtGroup.GET("/subnet", subnetGet)
virtGroup.GET("/certificate", certificatesGet)
virtGroup.GET("/secret", secretsGet)
virtGroup.PUT("/sync", syncPut)
dhcpGroup.PUT("/dhcp", dhcpPut)
}
func RegisterHost(engine *gin.Engine) {
engine.Use(AuthHost)
engine.Use(Recovery)
engine.Use(Errors)
engine.PUT("/sync", hostSyncPut)
engine.GET("/sync", hostSyncGet)
engine.GET("/state", hostStateGet)
}
================================================
FILE: imds/server/handlers/instance.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/config"
)
func instanceGet(c *gin.Context) {
c.JSON(200, config.Config.Instance)
}
================================================
FILE: imds/server/handlers/node.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/config"
)
func nodeGet(c *gin.Context) {
c.JSON(200, config.Config.Node)
}
================================================
FILE: imds/server/handlers/query.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/resource"
)
func queryGet(c *gin.Context) {
resrc := c.Param("resource")
key1 := c.Param("key1")
key2 := c.Param("key2")
key3 := c.Param("key3")
key4 := c.Param("key4")
keys := []string{}
if key1 != "" {
keys = append(keys, key1)
if key2 != "" {
keys = append(keys, key2)
if key3 != "" {
keys = append(keys, key3)
if key4 != "" {
keys = append(keys, key4)
}
}
}
}
val, err := resource.Query(resrc, keys...)
if err != nil {
c.AbortWithError(500, err)
return
}
c.String(200, val)
}
================================================
FILE: imds/server/handlers/secret.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/config"
)
func secretsGet(c *gin.Context) {
c.JSON(200, config.Config.Secrets)
}
================================================
FILE: imds/server/handlers/sync.go
================================================
package handlers
import (
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/dnss"
"github.com/pritunl/pritunl-cloud/imds/server/config"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
"github.com/pritunl/pritunl-cloud/imds/server/state"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/telemetry"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
lastSecurity = time.Now().Add(-7 * time.Minute)
lastSecurityLock sync.Mutex
)
type syncRespData struct {
Spec string `json:"spec"`
Hash uint32 `json:"hash"`
Journals []*types.Journal `json:"journals"`
}
func syncPut(c *gin.Context) {
data := &types.State{}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
if !state.Global.State.Final() {
state.Global.State.Status = data.Status
}
state.Global.State.Timestamp = time.Now()
state.Global.State.Memory = data.Memory
state.Global.State.HugePages = data.HugePages
state.Global.State.Load1 = data.Load1
state.Global.State.Load5 = data.Load5
state.Global.State.Load15 = data.Load15
if data.Updates != nil {
telemetry.Updates.Set(data.Updates)
}
if data.Output != nil {
for _, entry := range data.Output {
state.Global.AppendOutput(entry)
}
}
if data.Journals != nil {
for key, output := range data.Journals {
for _, entry := range output {
state.Global.AppendJournalOutput(key, entry)
}
}
}
if data.Hash != config.Config.Hash {
c.JSON(200, &syncRespData{
Spec: config.Config.SpecData,
Hash: config.Config.Hash,
Journals: config.Config.Journals,
})
} else {
c.JSON(200, &syncRespData{
Hash: config.Config.Hash,
Journals: config.Config.Journals,
})
}
}
func hostSyncPut(c *gin.Context) {
data := &types.Config{}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
if data.Hash != 0 {
config.Config = data
dnss.LoadConfig(data.Domains)
}
ste := state.Global.State.Copy()
ste.Hash = config.Config.Hash
ste.Output = state.Global.GetOutput()
ste.Journals = state.Global.GetJournals()
updates, ok := telemetry.Updates.Get()
if ok {
ste.Updates = updates
} else {
ste.Updates = nil
}
c.JSON(200, ste)
}
func hostSyncGet(c *gin.Context) {
ste := state.Global.State.Copy()
ste.Output = state.Global.GetOutput()
ste.Journals = state.Global.GetJournals()
updates, ok := telemetry.Updates.Get()
if ok {
ste.Updates = updates
} else {
ste.Updates = nil
}
c.JSON(200, ste)
}
func hostStateGet(c *gin.Context) {
ste := state.Global.State.Copy()
c.JSON(200, ste)
}
================================================
FILE: imds/server/handlers/vpc.go
================================================
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/imds/server/config"
)
func vpcGet(c *gin.Context) {
c.JSON(200, config.Config.Vpc)
}
func subnetGet(c *gin.Context) {
c.JSON(200, config.Config.Subnet)
}
================================================
FILE: imds/server/router/router.go
================================================
package router
import (
"context"
"fmt"
"net"
"net/http"
"os"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/dnss"
"github.com/pritunl/pritunl-cloud/imds/server/constants"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
"github.com/pritunl/pritunl-cloud/imds/server/handlers"
"github.com/pritunl/tools/logger"
)
type Router struct {
virtServer *http.Server
hostServer *http.Server
dnsServer *dnss.Server
}
func (r *Router) Run() (err error) {
logger.WithFields(logger.Fields{
"host": constants.Host,
"port": constants.Port,
"sock": constants.Sock,
"version": constants.Version,
}).Info("main: Starting imds server")
waiters := &sync.WaitGroup{}
waiters.Add(1)
go func() {
defer waiters.Done()
e := r.virtServer.ListenAndServe()
if e != nil {
e = &errortypes.WriteError{
errors.Wrap(e, "main: Server listen error"),
}
if err == nil {
err = e
}
r.Shutdown()
return
}
}()
waiters.Add(1)
go func() {
defer waiters.Done()
_ = os.Remove(constants.Sock)
listener, e := net.Listen("unix", constants.Sock)
if e != nil {
e = &errortypes.WriteError{
errors.Wrap(e, "main: Failed to create unix socket"),
}
if err == nil {
err = e
}
r.Shutdown()
return
}
e = r.hostServer.Serve(listener)
if e != nil {
e = &errortypes.WriteError{
errors.Wrap(e, "main: Server listen error"),
}
if err == nil {
err = e
}
r.Shutdown()
return
}
}()
waiters.Add(1)
go func() {
defer waiters.Done()
e := r.dnsServer.ListenUdp()
if e != nil {
if err == nil {
err = e
}
r.Shutdown()
return
}
}()
waiters.Add(1)
go func() {
defer waiters.Done()
e := r.dnsServer.ListenTcp()
if e != nil {
if err == nil {
err = e
}
r.Shutdown()
return
}
}()
waiters.Wait()
if err != nil {
return
}
return
}
func (r *Router) Shutdown() {
defer func() {
recover()
}()
webCtx, webCancel := context.WithTimeout(
context.Background(),
1*time.Second,
)
defer webCancel()
_ = r.virtServer.Shutdown(webCtx)
_ = r.virtServer.Close()
_ = r.hostServer.Shutdown(webCtx)
_ = r.hostServer.Close()
_ = r.dnsServer.Shutdown()
}
func (r *Router) Init() {
gin.SetMode(gin.ReleaseMode)
virtRouter := gin.New()
handlers.RegisterVirt(virtRouter)
r.virtServer = &http.Server{
Addr: fmt.Sprintf(
"%s:%d",
constants.Host,
constants.Port,
),
Handler: virtRouter,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 4096,
}
hostRouter := gin.New()
handlers.RegisterHost(hostRouter)
r.hostServer = &http.Server{
Addr: "127.0.0.1:99999",
Handler: hostRouter,
ReadTimeout: 30 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 4096,
}
r.dnsServer = dnss.NewServer(fmt.Sprintf(
"%s:53",
constants.Host,
))
return
}
================================================
FILE: imds/server/server.go
================================================
package server
import (
"flag"
"fmt"
"os"
"strings"
"github.com/pritunl/pritunl-cloud/imds/server/constants"
"github.com/pritunl/pritunl-cloud/imds/server/router"
"github.com/pritunl/pritunl-cloud/imds/server/state"
"github.com/pritunl/tools/logger"
)
func Main() (err error) {
constants.ClientSecret = os.Getenv("CLIENT_SECRET")
constants.DhcpSecret = os.Getenv("DHCP_SECRET")
constants.HostSecret = os.Getenv("HOST_SECRET")
os.Unsetenv("CLIENT_SECRET")
os.Unsetenv("DHCP_SECRET")
os.Unsetenv("HOST_SECRET")
logger.Init(
logger.SetTimeFormat(""),
)
logger.AddHandler(func(record *logger.Record) {
fmt.Print(record.String())
})
host := ""
flag.StringVar(&host, "host", "127.0.0.1", "Server bind address")
port := 0
flag.IntVar(&port, "port", 80, "Server bind port")
client := ""
flag.StringVar(&client, "client", "127.0.0.1", "Client address")
sockPath := ""
flag.StringVar(&sockPath, "sock", "", "Socket path")
flag.Parse()
constants.Host = strings.Split(host, "/")[0]
constants.Port = port
constants.Sock = sockPath
constants.Client = client
routr := &router.Router{}
routr.Init()
err = state.Init()
if err != nil {
return
}
err = routr.Run()
if err != nil {
return
}
return
}
================================================
FILE: imds/server/state/state.go
================================================
package state
import (
"sync"
"github.com/pritunl/pritunl-cloud/imds/types"
)
var Global = &Store{
State: &types.State{},
output: make(chan *types.Entry, 10000),
journals: map[string]chan *types.Entry{},
}
type Store struct {
State *types.State
output chan *types.Entry
journals map[string]chan *types.Entry
lock sync.RWMutex
}
func (s *Store) AppendOutput(entry *types.Entry) {
if len(s.output) > 9000 {
return
}
s.output <- entry
}
func (s *Store) GetOutput() (entries []*types.Entry) {
for {
select {
case entry := <-s.output:
entries = append(entries, entry)
default:
return
}
}
}
func (s *Store) AppendJournalOutput(key string, entry *types.Entry) {
s.lock.Lock()
output, exists := s.journals[key]
if !exists {
output = make(chan *types.Entry, 10000)
s.journals[key] = output
}
s.lock.Unlock()
if len(output) > 9000 {
return
}
output <- entry
}
func (s *Store) GetJournals() (journals map[string][]*types.Entry) {
journals = map[string][]*types.Entry{}
s.lock.RLock()
keys := make([]string, 0, len(s.journals))
outputs := make(map[string]chan *types.Entry)
for key, output := range s.journals {
keys = append(keys, key)
outputs[key] = output
}
s.lock.RUnlock()
for _, key := range keys {
output := outputs[key]
if output == nil {
continue
}
var entries []*types.Entry
for {
select {
case entry := <-output:
entries = append(entries, entry)
default:
if len(entries) > 0 {
journals[key] = entries
}
goto nextKey
}
}
nextKey:
}
return
}
func Init() (err error) {
return
}
================================================
FILE: imds/server/utils/files.go
================================================
package utils
import (
"bufio"
"io/ioutil"
"os"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var invalidPaths = set.NewSet("/", "", ".", "./")
func Chmod(pth string, mode os.FileMode) (err error) {
err = os.Chmod(pth, mode)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to chmod %s", pth),
}
return
}
return
}
func Exists(pth string) (exists bool, err error) {
_, err = os.Stat(pth)
if err == nil {
exists = true
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsDir(pth string) (exists bool, err error) {
stat, err := os.Stat(pth)
if err == nil {
exists = stat.IsDir()
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsFile(pth string) (exists bool, err error) {
stat, err := os.Stat(pth)
if err == nil {
exists = !stat.IsDir()
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsMkdir(pth string, perm os.FileMode) (err error) {
exists, err := ExistsDir(pth)
if err != nil {
return
}
if !exists {
err = os.MkdirAll(pth, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to mkdir %s", pth),
}
return
}
}
return
}
func ExistsRemove(pth string) (err error) {
exists, err := Exists(pth)
if err != nil {
return
}
if exists {
err = os.RemoveAll(pth)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to rm %s", pth),
}
return
}
}
return
}
func Remove(path string) (err error) {
if invalidPaths.Contains(path) {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Invalid remove path '%s'", path),
}
return
}
err = os.Remove(path)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to remove '%s'", path),
}
return
}
return
}
func RemoveAll(path string) (err error) {
if invalidPaths.Contains(path) {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Invalid remove path '%s'", path),
}
return
}
err = os.RemoveAll(path)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to remove '%s'", path),
}
return
}
return
}
func ContainsDir(pth string) (hasDir bool, err error) {
exists, err := ExistsDir(pth)
if !exists {
return
}
entries, err := ioutil.ReadDir(pth)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "queue: Failed to read dir %s", pth),
}
return
}
for _, entry := range entries {
if entry.IsDir() {
hasDir = true
return
}
}
return
}
func Open(path string, perm os.FileMode) (file *os.File, err error) {
file, err = os.OpenFile(path, os.O_RDWR|os.O_TRUNC, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to open '%s'", path),
}
return
}
return
}
func Read(path string) (data string, err error) {
dataByt, err := ioutil.ReadFile(path)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", path),
}
return
}
data = string(dataByt)
return
}
func ReadLines(path string) (lines []string, err error) {
file, err := os.Open(path)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to open '%s'", path),
}
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", path),
}
return
}
}()
lines = []string{}
reader := bufio.NewReader(file)
for {
line, e := reader.ReadString('\n')
if e != nil {
break
}
lines = append(lines, strings.Trim(line, "\n"))
}
return
}
func Write(path string, data string, perm os.FileMode) (err error) {
file, err := Open(path, perm)
if err != nil {
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write '%s'", path),
}
return
}
}()
_, err = file.WriteString(data)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write to file '%s'", path),
}
return
}
return
}
func Create(path string, perm os.FileMode) (file *os.File, err error) {
file, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to create '%s'", path),
}
return
}
return
}
func CreateWrite(path string, data string, perm os.FileMode) (err error) {
file, err := Create(path, perm)
if err != nil {
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write '%s'", path),
}
return
}
}()
_, err = file.WriteString(data)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write to file '%s'", path),
}
return
}
return
}
================================================
FILE: imds/server/utils/misc.go
================================================
package utils
func StringsContains(val []string, str string) bool {
if val == nil {
return false
}
for _, v := range val {
if v == str {
return true
}
}
return false
}
================================================
FILE: imds/server/utils/request.go
================================================
package utils
import (
"strings"
)
func StripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return hostport
}
n := strings.Count(hostport, ":")
if n > 1 {
if i := strings.IndexByte(hostport, ']'); i != -1 {
return strings.TrimPrefix(hostport[:i], "[")
}
return hostport
}
return hostport[:colon]
}
================================================
FILE: imds/systemd.go
================================================
package imds
import (
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/features"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/permission"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
const systemdNamespaceTemplate = `[Unit]
Description=Pritunl Cloud IMDS Server
After=network.target
[Service]
Type=simple
User=%s
Environment="CLIENT_SECRET=%s"
Environment="DHCP_SECRET=%s"
Environment="HOST_SECRET=%s"
ExecStart=/usr/bin/pritunl-cloud -sock=%s -host=%s -port=%d imds-server
TimeoutStopSec=5
Restart=always
RestartSec=3
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
NetworkNamespacePath=/var/run/netns/%s
AmbientCapabilities=CAP_NET_BIND_SERVICE
`
const systemdTemplate = `[Unit]
Description=Pritunl Cloud IMDS Server
After=network.target
[Service]
Type=simple
User=root
Environment="CLIENT_SECRET=%s"
Environment="DHCP_SECRET=%s"
Environment="HOST_SECRET=%s"
ExecStart=/usr/sbin/ip netns exec %s /usr/bin/pritunl-cloud -sock=%s -host=%s -port=%d imds-server
TimeoutStopSec=5
Restart=always
RestartSec=3
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
AmbientCapabilities=CAP_NET_BIND_SERVICE
`
func WriteService(vmId bson.ObjectID,
namespace, clientSecret, dhcpSecret, hostSecret string,
systemdNamespace bool) (err error) {
unitPath := paths.GetUnitPathImds(vmId)
sockPath := paths.GetImdsSockPath(vmId)
if clientSecret == "" || dhcpSecret == "" || hostSecret == "" {
err = &errortypes.ParseError{
errors.New("imds: Cannot start imds with empty secret"),
}
return
}
output := ""
if systemdNamespace {
output = fmt.Sprintf(
systemdNamespaceTemplate,
permission.GetUserName(vmId),
clientSecret,
dhcpSecret,
hostSecret,
sockPath,
settings.Hypervisor.ImdsAddress,
settings.Hypervisor.ImdsPort,
namespace,
)
} else {
output = fmt.Sprintf(
systemdTemplate,
clientSecret,
dhcpSecret,
hostSecret,
namespace,
sockPath,
settings.Hypervisor.ImdsAddress,
settings.Hypervisor.ImdsPort,
)
}
err = utils.CreateWrite(unitPath, output, 0600)
if err != nil {
return
}
return
}
func Start(db *database.Database, virt *vm.VirtualMachine) (err error) {
namespace := vm.GetNamespace(virt.Id, 0)
hasSystemdNamespace := features.HasSystemdNamespace()
unit := paths.GetUnitNameImds(virt.Id)
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
"systemd_unit": unit,
}).Info("imds: Starting virtual machine imds server")
_ = systemd.Stop(unit)
err = WriteService(virt.Id, namespace, virt.ImdsClientSecret,
virt.ImdsDhcpSecret, virt.ImdsHostSecret, hasSystemdNamespace)
if err != nil {
return
}
err = systemd.Reload()
if err != nil {
return
}
err = systemd.Start(unit)
if err != nil {
return
}
return
}
func Restart(instId bson.ObjectID) (err error) {
unit := paths.GetUnitNameImds(instId)
_ = systemd.Restart(unit)
return
}
func Stop(virt *vm.VirtualMachine) (err error) {
unit := paths.GetUnitNameImds(virt.Id)
_ = systemd.Stop(unit)
return
}
================================================
FILE: imds/types/certificate.go
================================================
package types
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/certificate"
)
type Certificate struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Key string `json:"key"`
Certificate string `json:"certificate"`
}
func NewCertificates(certs []*certificate.Certificate) []*Certificate {
datas := []*Certificate{}
for _, cert := range certs {
if cert == nil {
continue
}
data := &Certificate{
Id: cert.Id,
Name: cert.Name,
Type: cert.Type,
Key: cert.Key,
Certificate: cert.Certificate,
}
datas = append(datas, data)
}
return datas
}
================================================
FILE: imds/types/config.go
================================================
package types
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/utils"
)
// Cannot contain maps for encode order
type Config struct {
Spec bson.ObjectID `json:"spec"`
SpecData string `json:"spec_data" gob:"-"`
ImdsHostSecret string `json:"-"`
ClientIps []string `json:"client_ips"`
Node *Node `json:"node"`
Instance *Instance `json:"instance"`
Vpc *Vpc `json:"vpc"`
Subnet *Subnet `json:"subnet"`
Certificates []*Certificate `json:"certificates"`
Secrets []*Secret `json:"secrets"`
Pods []*Pod `json:"pods"`
Journals []*Journal `json:"journals"`
Domains []*Domain `json:"domains"`
Hash uint32 `json:"hash"`
}
func (c *Config) ComputeHash() (err error) {
c.Hash = 0
confHash, err := utils.CrcHash(c)
if err != nil {
return
}
c.Hash = confHash
return
}
================================================
FILE: imds/types/constants.go
================================================
package types
const (
Initializing = "initializing"
ReloadingClean = "reloading_clean"
ReloadingFault = "reloading_fault"
Running = "running"
Fault = "fault"
Offline = "offline"
Imaged = "imaged"
)
================================================
FILE: imds/types/domain.go
================================================
package types
import (
"net"
)
type Domain struct {
Domain string `json:"domain"`
Type string `json:"type"`
Ip net.IP `json:"ip"`
Target string `json:"target"`
}
================================================
FILE: imds/types/instance.go
================================================
package types
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
)
type Instance struct {
Id bson.ObjectID `json:"id"`
Organization bson.ObjectID `json:"organization"`
Zone bson.ObjectID `json:"zone"`
Vpc bson.ObjectID `json:"vpc"`
Subnet bson.ObjectID `json:"subnet"`
CloudSubnet string `json:"cloud_subnet"`
CloudVnic string `json:"cloud_vnic"`
Image bson.ObjectID `json:"image"`
State string `json:"state"`
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
Uefi bool `json:"uefi"`
SecureBoot bool `json:"secure_boot"`
Tpm bool `json:"tpm"`
DhcpServer bool `json:"dhcp_server"`
CloudType string `json:"cloud_type"`
SystemKind string `json:"system_kind"`
DeleteProtection bool `json:"delete_protection"`
SkipSourceDestCheck bool `json:"skip_source_dest_check"`
QemuVersion string `json:"qemu_version"`
PublicIps []string `json:"public_ips"`
PublicIps6 []string `json:"public_ips6"`
PrivateIps []string `json:"private_ips"`
PrivateIps6 []string `json:"private_ips6"`
GatewayIps []string `json:"gateway_ips"`
GatewayIps6 []string `json:"gateway_ips6"`
CloudPrivateIps []string `json:"cloud_private_ips"`
CloudPublicIps []string `json:"cloud_public_ips"`
CloudPublicIps6 []string `json:"cloud_public_ips6"`
HostIps []string `json:"host_ips"`
NodePortIps []string `json:"node_port_ips"`
NetworkNamespace string `json:"network_namespace"`
NoPublicAddress bool `json:"no_public_address"`
NoPublicAddress6 bool `json:"no_public_address6"`
NoHostAddress bool `json:"no_host_address"`
Node bson.ObjectID `json:"node"`
Shape bson.ObjectID `json:"shape"`
Name string `json:"name"`
RootEnabled bool `json:"root_enabled"`
Memory int `json:"memory"`
Processors int `json:"processors"`
Roles []string `json:"roles"`
Vnc bool `json:"vnc"`
Spice bool `json:"spice"`
Gui bool `json:"gui"`
Deployment bson.ObjectID `json:"deployment"`
}
func NewInstance(inst *instance.Instance) *Instance {
if inst == nil {
return &Instance{}
}
return &Instance{
Id: inst.Id,
Organization: inst.Organization,
Zone: inst.Zone,
Vpc: inst.Vpc,
Subnet: inst.Subnet,
CloudSubnet: inst.CloudSubnet,
CloudVnic: inst.CloudVnic,
Image: inst.Image,
State: inst.State,
Timestamp: inst.Timestamp,
Action: inst.Action,
Uefi: inst.Uefi,
SecureBoot: inst.SecureBoot,
Tpm: inst.Tpm,
DhcpServer: inst.DhcpServer,
CloudType: inst.CloudType,
SystemKind: inst.SystemKind,
DeleteProtection: inst.DeleteProtection,
SkipSourceDestCheck: inst.SkipSourceDestCheck,
QemuVersion: inst.QemuVersion,
PublicIps: inst.PublicIps,
PublicIps6: inst.PublicIps6,
PrivateIps: inst.PrivateIps,
PrivateIps6: inst.PrivateIps6,
GatewayIps: inst.GatewayIps,
GatewayIps6: inst.GatewayIps6,
CloudPrivateIps: inst.CloudPrivateIps,
CloudPublicIps: inst.CloudPublicIps,
CloudPublicIps6: inst.CloudPublicIps6,
HostIps: inst.HostIps,
NodePortIps: inst.NodePortIps,
NetworkNamespace: inst.NetworkNamespace,
NoPublicAddress: inst.NoPublicAddress,
NoPublicAddress6: inst.NoPublicAddress6,
NoHostAddress: inst.NoHostAddress,
Node: inst.Node,
Shape: inst.Shape,
Name: inst.Name,
RootEnabled: inst.RootEnabled,
Memory: inst.Memory,
Processors: inst.Processors,
Roles: inst.Roles,
Vnc: inst.Vnc,
Spice: inst.Spice,
Gui: inst.Gui,
Deployment: inst.Deployment,
}
}
================================================
FILE: imds/types/journal.go
================================================
package types
import (
"github.com/pritunl/pritunl-cloud/spec"
)
type Journal struct {
Index int32 `json:"index"`
Key string `json:"key"`
Type string `json:"type"`
Unit string `json:"unit"`
Path string `json:"path"`
}
func NewJournals(spc *spec.Spec) []*Journal {
if spc == nil || spc.Journal == nil {
return nil
}
jrnls := []*Journal{}
for _, jrnl := range spc.Journal.Inputs {
jrnls = append(jrnls, &Journal{
Index: jrnl.Index,
Key: jrnl.Key,
Type: jrnl.Type,
Unit: jrnl.Unit,
Path: jrnl.Path,
})
}
return jrnls
}
================================================
FILE: imds/types/node.go
================================================
package types
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/node"
)
type Node struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
PublicIps []string `json:"public_ips"`
PublicIps6 []string `json:"public_ips6"`
}
func NewNode(nde *node.Node) *Node {
if nde == nil {
return &Node{}
}
return &Node{
Id: nde.Id,
Name: nde.Name,
PublicIps: nde.PublicIps,
PublicIps6: nde.PublicIps6,
}
}
================================================
FILE: imds/types/pod.go
================================================
package types
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/unit"
)
type Pod struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Units []*Unit `json:"units"`
}
type Unit struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Kind string `json:"kind"`
Count int `json:"count"`
PublicIps []string `json:"public_ips"`
PublicIps6 []string `json:"public_ips6"`
HealthyPublicIps []string `json:"healthy_public_ips"`
HealthyPublicIps6 []string `json:"healthy_public_ips6"`
UnhealthyPublicIps []string `json:"unhealthy_public_ips"`
UnhealthyPublicIps6 []string `json:"unhealthy_public_ips6"`
PrivateIps []string `json:"private_ips"`
PrivateIps6 []string `json:"private_ips6"`
HealthyPrivateIps []string `json:"healthy_private_ips"`
HealthyPrivateIps6 []string `json:"healthy_private_ips6"`
UnhealthyPrivateIps []string `json:"unhealthy_private_ips"`
UnhealthyPrivateIps6 []string `json:"unhealthy_private_ips6"`
CloudPublicIps []string `json:"cloud_public_ips"`
CloudPublicIps6 []string `json:"cloud_public_ips6"`
CloudPrivateIps []string `json:"cloud_private_ips"`
HealthyCloudPublicIps []string `json:"healthy_cloud_public_ips"`
HealthyCloudPublicIps6 []string `json:"healthy_cloud_public_ips6"`
HealthyCloudPrivateIps []string `json:"healthy_cloud_private_ips"`
UnhealthyCloudPublicIps []string `json:"unhealthy_cloud_public_ips"`
UnhealthyCloudPublicIps6 []string `json:"unhealthy_cloud_public_ips6"`
UnhealthyCloudPrivateIps []string `json:"unhealthy_cloud_private_ips"`
}
func NewPods(pods []*pod.Pod, podUnitsMap map[bson.ObjectID][]*unit.Unit,
deployments map[bson.ObjectID]*deployment.Deployment) []*Pod {
datas := []*Pod{}
for _, pd := range pods {
if pd == nil {
continue
}
units := []*Unit{}
for _, pdUnit := range podUnitsMap[pd.Id] {
unit := &Unit{
Id: pdUnit.Id,
Name: pdUnit.Name,
Kind: pdUnit.Kind,
Count: pdUnit.Count,
PublicIps: []string{},
PublicIps6: []string{},
HealthyPublicIps: []string{},
HealthyPublicIps6: []string{},
UnhealthyPublicIps: []string{},
UnhealthyPublicIps6: []string{},
PrivateIps: []string{},
PrivateIps6: []string{},
HealthyPrivateIps: []string{},
HealthyPrivateIps6: []string{},
UnhealthyPrivateIps: []string{},
UnhealthyPrivateIps6: []string{},
CloudPublicIps: []string{},
CloudPublicIps6: []string{},
CloudPrivateIps: []string{},
HealthyCloudPublicIps: []string{},
HealthyCloudPublicIps6: []string{},
HealthyCloudPrivateIps: []string{},
UnhealthyCloudPublicIps: []string{},
UnhealthyCloudPublicIps6: []string{},
UnhealthyCloudPrivateIps: []string{},
}
for _, unitDeplyId := range pdUnit.Deployments {
deply := deployments[unitDeplyId]
if deply != nil {
data := deply.InstanceData
if data == nil {
data = &deployment.InstanceData{}
}
publicIps := data.PublicIps
if publicIps == nil {
publicIps = []string{}
}
publicIps6 := data.PublicIps6
if publicIps6 == nil {
publicIps6 = []string{}
}
healthyPublicIps := []string{}
unhealthyPublicIps := []string{}
healthyPublicIps6 := []string{}
unhealthyPublicIps6 := []string{}
privateIps := data.PrivateIps
if privateIps == nil {
privateIps = []string{}
}
privateIps6 := data.PrivateIps6
if privateIps6 == nil {
privateIps6 = []string{}
}
healthyPrivateIps := []string{}
unhealthyPrivateIps := []string{}
healthyPrivateIps6 := []string{}
unhealthyPrivateIps6 := []string{}
cloudPublicIps := data.CloudPublicIps
if cloudPublicIps == nil {
cloudPublicIps = []string{}
}
cloudPublicIps6 := data.CloudPublicIps6
if cloudPublicIps6 == nil {
cloudPublicIps6 = []string{}
}
cloudPrivateIps := data.CloudPrivateIps
if cloudPrivateIps == nil {
cloudPrivateIps = []string{}
}
healthyCloudPublicIps := []string{}
unhealthyCloudPublicIps := []string{}
healthyCloudPublicIps6 := []string{}
unhealthyCloudPublicIps6 := []string{}
healthyCloudPrivateIps := []string{}
unhealthyCloudPrivateIps := []string{}
if deply.IsHealthy() {
healthyPublicIps = publicIps
healthyPublicIps6 = publicIps6
healthyPrivateIps = privateIps
healthyPrivateIps6 = privateIps6
healthyCloudPublicIps = cloudPublicIps
healthyCloudPublicIps6 = cloudPublicIps6
healthyCloudPrivateIps = cloudPrivateIps
} else {
unhealthyPublicIps = publicIps
unhealthyPublicIps6 = publicIps6
unhealthyPrivateIps = privateIps
unhealthyPrivateIps6 = privateIps6
unhealthyCloudPublicIps = cloudPublicIps
unhealthyCloudPublicIps6 = cloudPublicIps6
unhealthyCloudPrivateIps = cloudPrivateIps
}
unit.PublicIps = append(
unit.PublicIps, publicIps...)
unit.PublicIps6 = append(
unit.PublicIps6, publicIps6...)
unit.HealthyPublicIps = append(
unit.HealthyPublicIps, healthyPublicIps...)
unit.HealthyPublicIps6 = append(
unit.HealthyPublicIps6, healthyPublicIps6...)
unit.UnhealthyPublicIps = append(
unit.UnhealthyPublicIps, unhealthyPublicIps...)
unit.UnhealthyPublicIps6 = append(
unit.UnhealthyPublicIps6, unhealthyPublicIps6...)
unit.PrivateIps = append(
unit.PrivateIps, privateIps...)
unit.PrivateIps6 = append(
unit.PrivateIps6, privateIps6...)
unit.HealthyPrivateIps = append(
unit.HealthyPrivateIps, healthyPrivateIps...)
unit.HealthyPrivateIps6 = append(
unit.HealthyPrivateIps6, healthyPrivateIps6...)
unit.UnhealthyPrivateIps = append(
unit.UnhealthyPrivateIps, unhealthyPrivateIps...)
unit.UnhealthyPrivateIps6 = append(
unit.UnhealthyPrivateIps6, unhealthyPrivateIps6...)
unit.CloudPublicIps = append(
unit.CloudPublicIps, cloudPublicIps...)
unit.HealthyCloudPublicIps = append(
unit.HealthyCloudPublicIps,
healthyCloudPublicIps...,
)
unit.UnhealthyCloudPublicIps = append(
unit.UnhealthyCloudPublicIps,
unhealthyCloudPublicIps...,
)
unit.CloudPublicIps6 = append(
unit.CloudPublicIps6, cloudPublicIps6...)
unit.HealthyCloudPublicIps6 = append(
unit.HealthyCloudPublicIps6,
healthyCloudPublicIps6...,
)
unit.UnhealthyCloudPublicIps6 = append(
unit.UnhealthyCloudPublicIps6,
unhealthyCloudPublicIps6...,
)
unit.CloudPrivateIps = append(
unit.CloudPrivateIps, cloudPrivateIps...)
unit.HealthyCloudPrivateIps = append(
unit.HealthyCloudPrivateIps,
healthyCloudPrivateIps...,
)
unit.UnhealthyCloudPrivateIps = append(
unit.UnhealthyCloudPrivateIps,
unhealthyCloudPrivateIps...,
)
}
}
units = append(units, unit)
}
data := &Pod{
Id: pd.Id,
Name: pd.Name,
Units: units,
}
datas = append(datas, data)
}
return datas
}
================================================
FILE: imds/types/secret.go
================================================
package types
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/secret"
)
type Secret struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
Key string `json:"key"`
Value string `json:"value"`
Data string `json:"data"`
Region string `json:"region"`
PublicKey string `json:"public_key"`
PrivateKey string `json:"private_key"`
}
func NewSecrets(secrs []*secret.Secret) []*Secret {
datas := []*Secret{}
for _, secr := range secrs {
if secr == nil {
continue
}
data := &Secret{
Id: secr.Id,
Name: secr.Name,
Type: secr.Type,
Key: secr.Key,
Value: secr.Value,
Data: secr.Data,
Region: secr.Region,
PublicKey: secr.PublicKey,
PrivateKey: secr.PrivateKey,
}
datas = append(datas, data)
}
return datas
}
================================================
FILE: imds/types/state.go
================================================
package types
import (
"net"
"time"
"github.com/pritunl/pritunl-cloud/telemetry"
)
type State struct {
Hash uint32 `json:"hash"`
Status string `json:"status"`
Memory float64 `json:"memory"`
HugePages float64 `json:"hugepages"`
Load1 float64 `json:"load1"`
Load5 float64 `json:"load5"`
Load15 float64 `json:"load15"`
DhcpIface string `json:"dhcp_iface"`
DhcpIface6 string `json:"dhcp_iface6"`
DhcpIp *net.IPNet `json:"dhcp_ip"`
DhcpIp6 *net.IPNet `json:"dhcp_ip6"`
DhcpGateway net.IP `json:"dhcp_gateway"`
Updates []*telemetry.Update `json:"updates"`
Timestamp time.Time `json:"timestamp"`
Output []*Entry `json:"output,omitempty"`
Journals map[string][]*Entry `json:"journals,omitempty"`
}
func (s *State) Final() bool {
if s.Status == Imaged {
return true
}
return false
}
func (s *State) Copy() *State {
return &State{
Hash: s.Hash,
Status: s.Status,
Memory: s.Memory,
HugePages: s.HugePages,
Load1: s.Load1,
Load5: s.Load5,
Load15: s.Load15,
DhcpIface: s.DhcpIface,
DhcpIface6: s.DhcpIface6,
DhcpIp: s.DhcpIp,
DhcpIp6: s.DhcpIp6,
DhcpGateway: s.DhcpGateway,
Updates: s.Updates,
Timestamp: s.Timestamp,
}
}
type Entry struct {
Timestamp time.Time `json:"t"`
Level int32 `json:"l"`
Message string `json:"m"`
}
const (
Error = 3
Info = 5
)
================================================
FILE: imds/types/vpc.go
================================================
package types
import (
"fmt"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/vpc"
)
type Vpc struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
VpcId int `json:"vpc_id"`
Network string `json:"network"`
Network6 string `json:"network6"`
Subnets []*Subnet `json:"subnets"`
Routes []*Route `json:"routes"`
}
type Subnet struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Network string `json:"network"`
}
func (s *Subnet) String() string {
return s.Network
}
type Route struct {
Destination string `json:"destination"`
Target string `json:"target"`
}
func (r *Route) String() string {
return fmt.Sprintf("%s via %s", r.Destination, r.Target)
}
func NewSubnet(subnet *vpc.Subnet) *Subnet {
if subnet == nil {
return &Subnet{}
}
return &Subnet{
Id: subnet.Id,
Name: subnet.Name,
Network: subnet.Network,
}
}
func NewRoute(subnet *vpc.Route) *Route {
if subnet == nil {
return &Route{}
}
return &Route{
Destination: subnet.Destination,
Target: subnet.Target,
}
}
func NewVpc(vpc *vpc.Vpc) *Vpc {
if vpc == nil {
return &Vpc{}
}
vpc.Json()
subnets := []*Subnet{}
if vpc.Subnets != nil {
for _, subnet := range vpc.Subnets {
subnets = append(subnets, NewSubnet(subnet))
}
}
routes := []*Route{}
if vpc.Routes != nil {
for _, route := range vpc.Routes {
routes = append(routes, NewRoute(route))
}
}
return &Vpc{
Id: vpc.Id,
Name: vpc.Name,
VpcId: vpc.VpcId,
Network: vpc.Network,
Network6: vpc.Network6,
Subnets: subnets,
Routes: routes,
}
}
================================================
FILE: info/instance.go
================================================
package info
import (
"fmt"
"sort"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/state"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/tools/set"
)
func NewInstance(stat *state.State, inst *instance.Instance) (
inf *instance.Info) {
inf = &instance.Info{
Timestamp: time.Now().Add(time.Duration(
utils.RandInt(0, 10)) * time.Second),
Disks: []string{},
FirewallRules: map[string]string{},
Authorities: []string{},
CloudSubnets: []*node.CloudSubnet{},
}
nde := stat.Node()
if inst.Node != nde.Id {
return
}
inf.Node = nde.Name
if len(nde.PublicIps) > 0 {
inf.NodePublicIp = nde.PublicIps[0]
}
inf.Iscsi = nde.Iscsi
inf.Isos = nde.LocalIsos
inf.CloudSubnets = nde.GetCloudSubnetsName()
if nde.UsbPassthrough {
inf.UsbDevices = nde.UsbDevices
}
if nde.PciDevices != nil {
inf.PciDevices = nde.PciDevices
}
if nde.InstanceDrives != nil {
inf.DriveDevices = nde.InstanceDrives
}
dc := stat.NodeDatacenter()
if dc != nil {
inf.Mtu = dc.GetInstanceMtu()
}
instDisks := stat.GetInstaceDisks(inst.Id)
for _, dsk := range instDisks {
inf.Disks = append(
inf.Disks,
fmt.Sprintf("%s: %s", dsk.Index, dsk.Name),
)
}
firewallRulesKeys := []string{}
firewallRules := map[string]set.Set{}
namespaces := stat.GetInstanceNamespaces(inst.Id)
firewalls := stat.Firewalls()
for _, namespace := range namespaces {
for _, rule := range firewalls[namespace] {
key := rule.Protocol
if rule.Port != "" {
key += ":" + rule.Port
}
rules := firewallRules[key]
if rules == nil {
rules = set.NewSet()
firewallRules[key] = rules
firewallRulesKeys = append(
firewallRulesKeys,
key,
)
}
for _, sourceIp := range rule.SourceIps {
rules.Add(sourceIp)
}
}
}
sort.Strings(firewallRulesKeys)
for _, key := range firewallRulesKeys {
rules := firewallRules[key]
vals := []string{}
for rule := range rules.Iter() {
vals = append(vals, rule.(string))
}
sort.Strings(vals)
inf.FirewallRules[key] = strings.Join(vals, ", ")
}
authrs := stat.GetInstaceAuthorities(inst.Organization, inst.Roles)
for _, authr := range authrs {
inf.Authorities = append(inf.Authorities, authr.Name)
}
sort.Strings(inf.Authorities)
return
}
================================================
FILE: instance/constants.go
================================================
package instance
import (
"github.com/dropbox/godropbox/container/set"
)
const (
Starting = "starting"
Running = "running"
Stopped = "stopped"
Failed = "failed"
Updating = "updating"
Provisioning = "provisioning"
Bridge = "bridge"
Vxlan = "vxlan"
Start = "start"
Stop = "stop"
Cleanup = "cleanup"
Restart = "restart"
Destroy = "destroy"
Linux = "linux"
LinuxLegacy = "linux_legacy"
BSD = "bsd"
AlpineLinux = "alpinelinux"
ArchLinux = "archlinux"
RedHat = "redhat"
Fedora = "fedora"
Ubuntu = "ubuntu"
FreeBSD = "freebsd"
HostPath = "host_path"
)
var (
ValidStates = set.NewSet(
Starting,
Running,
Stopped,
Failed,
Updating,
Provisioning,
Bridge,
Vxlan,
)
ValidActions = set.NewSet(
Start,
Stop,
Cleanup,
Restart,
Destroy,
)
ValidCloudTypes = set.NewSet(
Linux,
LinuxLegacy,
BSD,
)
ValidSystemKinds = set.NewSet(
AlpineLinux,
ArchLinux,
RedHat,
Fedora,
Ubuntu,
FreeBSD,
)
)
================================================
FILE: instance/errortypes.go
================================================
package instance
import (
"github.com/dropbox/godropbox/errors"
)
type VncDialError struct {
errors.DropboxError
}
================================================
FILE: instance/instance.go
================================================
package instance
import (
"fmt"
"math/rand"
"net/http"
"regexp"
"strconv"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gorilla/websocket"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/drive"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/iscsi"
"github.com/pritunl/pritunl-cloud/iso"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/pci"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/shape"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/telemetry"
"github.com/pritunl/pritunl-cloud/tpm"
"github.com/pritunl/pritunl-cloud/usb"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
var scriptReg = regexp.MustCompile("^#!")
type Instance struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
UnixId int `bson:"unix_id" json:"unix_id"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
Zone bson.ObjectID `bson:"zone" json:"zone"`
Vpc bson.ObjectID `bson:"vpc" json:"vpc"`
Subnet bson.ObjectID `bson:"subnet" json:"subnet"`
Created time.Time `bson:"created" json:"created"`
Guest *GuestData `bson:"guest,omitempty" json:"guest"`
CloudSubnet string `bson:"cloud_subnet" json:"cloud_subnet"`
CloudVnic string `bson:"cloud_vnic" json:"cloud_vnic"`
CloudVnicAttach string `bson:"cloud_vnic_attach" json:"cloud_vnic_attach"`
Image bson.ObjectID `bson:"image" json:"image"`
ImageBacking bool `bson:"image_backing" json:"image_backing"`
DiskType string `bson:"disk_type" json:"disk_type"`
DiskPool bson.ObjectID `bson:"disk_pool" json:"disk_pool"`
Status string `bson:"-" json:"status"`
StatusInfo *StatusInfo `bson:"status_info,omitempty" json:"status_info"`
Uptime string `bson:"-" json:"uptime"`
State string `bson:"state" json:"state"`
Action string `bson:"action" json:"action"`
PublicMac string `bson:"-" json:"public_mac"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Restart bool `bson:"restart" json:"restart"`
RestartReason string `bson:"restart_reason" json:"restart_reason"`
RestartBlockIp bool `bson:"restart_block_ip" json:"restart_block_ip"`
Uefi bool `bson:"uefi" json:"uefi"`
SecureBoot bool `bson:"secure_boot" json:"secure_boot"`
Tpm bool `bson:"tpm" json:"tpm"`
TpmSecret string `bson:"tpm_secret" json:"-"`
DhcpServer bool `bson:"dhcp_server" json:"dhcp_server"`
CloudType string `bson:"cloud_type" json:"cloud_type"`
CloudScript string `bson:"cloud_script" json:"cloud_script"`
SystemKind string `bson:"system_kind" json:"system_kind"`
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"`
SkipSourceDestCheck bool `bson:"skip_source_dest_check" json:"skip_source_dest_check"`
QemuVersion string `bson:"qemu_version" json:"qemu_version"`
PublicIps []string `bson:"public_ips" json:"public_ips"`
PublicIps6 []string `bson:"public_ips6" json:"public_ips6"`
PrivateIps []string `bson:"private_ips" json:"private_ips"`
PrivateIps6 []string `bson:"private_ips6" json:"private_ips6"`
GatewayIps []string `bson:"gateway_ips" json:"gateway_ips"`
GatewayIps6 []string `bson:"gateway_ips6" json:"gateway_ips6"`
CloudPrivateIps []string `bson:"cloud_private_ips" json:"cloud_private_ips"`
CloudPublicIps []string `bson:"cloud_public_ips" json:"cloud_public_ips"`
CloudPublicIps6 []string `bson:"cloud_public_ips6" json:"cloud_public_ips6"`
HostIps []string `bson:"host_ips" json:"host_ips"`
NodePortIps []string `bson:"node_port_ips" json:"node_port_ips"`
NodePorts []*nodeport.Mapping `bson:"node_ports,omitempty" json:"node_ports"`
DhcpIp string `bson:"dhcp_ip" json:"dhcp_ip"`
DhcpIp6 string `bson:"dhcp_ip6" json:"dhcp_ip6"`
NetworkNamespace string `bson:"network_namespace" json:"network_namespace"`
NoPublicAddress bool `bson:"no_public_address" json:"no_public_address"`
NoPublicAddress6 bool `bson:"no_public_address6" json:"no_public_address6"`
NoHostAddress bool `bson:"no_host_address" json:"no_host_address"`
Node bson.ObjectID `bson:"node" json:"node"`
Shape bson.ObjectID `bson:"shape" json:"shape"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
RootEnabled bool `bson:"root_enabled" json:"root_enabled"`
RootPasswd string `bson:"root_passwd" json:"root_passwd"`
InitDiskSize int `bson:"init_disk_size" json:"init_disk_size"`
Memory int `bson:"memory" json:"memory"`
Processors int `bson:"processors" json:"processors"`
Roles []string `bson:"roles" json:"roles"`
Isos []*iso.Iso `bson:"isos,omitempty" json:"isos"`
UsbDevices []*usb.Device `bson:"usb_devices,omitempty" json:"usb_devices"`
PciDevices []*pci.Device `bson:"pci_devices,omitempty" json:"pci_devices"`
DriveDevices []*drive.Device `bson:"drive_devices,omitempty" json:"drive_devices"`
IscsiDevices []*iscsi.Device `bson:"iscsi_devices,omitempty" json:"iscsi_devices"`
Mounts []*Mount `bson:"mounts,omitempty" json:"mounts"`
Vnc bool `bson:"vnc" json:"vnc"`
VncPassword string `bson:"vnc_password" json:"vnc_password"`
VncDisplay int `bson:"vnc_display" json:"vnc_display"`
Spice bool `bson:"spice" json:"spice"`
SpicePassword string `bson:"spice_password" json:"spice_password"`
SpicePort int `bson:"spice_port" json:"spice_port"`
Gui bool `bson:"gui" json:"gui"`
Deployment bson.ObjectID `bson:"deployment" json:"deployment"`
Info *Info `bson:"info,omitempty" json:"info"`
Virt *vm.VirtualMachine `bson:"-" json:"-"`
curVpc bson.ObjectID `bson:"-" json:"-"`
curSubnet bson.ObjectID `bson:"-" json:"-"`
curDeleteProtection bool `bson:"-" json:"-"`
curAction string `bson:"-" json:"-"`
curNoPublicAddress bool `bson:"-" json:"-"`
curNoHostAddress bool `bson:"-" json:"-"`
curNodePorts map[bson.ObjectID]*nodeport.Mapping `bson:"-" json:"-"`
removedNodePorts []bson.ObjectID `bson:"-" json:"-"`
newId bool `bson:"-" json:"-"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Zone bson.ObjectID `bson:"zone" json:"zone"`
Vpc bson.ObjectID `bson:"vpc" json:"vpc"`
Subnet bson.ObjectID `bson:"subnet" json:"subnet"`
Node bson.ObjectID `bson:"node" json:"node"`
}
type Mount struct {
Name string `bson:"name" json:"name"`
Type string `bson:"type" json:"type"`
Path string `bson:"path" json:"path"`
HostPath string `bson:"host_path" json:"host_path"`
}
type StatusInfo struct {
DownloadProgress int `bson:"download_progress,omitempty" json:"download_progress"`
DownloadSpeed float64 `bson:"download_speed,omitempty" json:"download_speed"`
}
type GuestData struct {
Status string `bson:"status" json:"status"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Heartbeat time.Time `bson:"heartbeat" json:"heartbeat"`
Memory float64 `bson:"memory" json:"memory"`
HugePages float64 `bson:"hugepages" json:"hugepages"`
Load1 float64 `bson:"load1" json:"load1"`
Load5 float64 `bson:"load5" json:"load5"`
Load15 float64 `bson:"load15" json:"load15"`
Updates []*telemetry.Update `bson:"updates" json:"updates"`
}
type Info struct {
Node string `bson:"node" json:"node"`
NodePublicIp string `bson:"node_public_ip" json:"node_public_ip"`
Mtu int `bson:"mtu" json:"mtu"`
Iscsi bool `bson:"iscsi" json:"iscsi"`
Disks []string `bson:"disks" json:"disks"`
FirewallRules map[string]string `bson:"firewall_rules" json:"firewall_rules"`
Authorities []string `bson:"authorities" json:"authorities"`
Isos []*iso.Iso `bson:"isos" json:"isos"`
UsbDevices []*usb.Device `bson:"usb_devices" json:"usb_devices"`
PciDevices []*pci.Device `bson:"pci_devices" json:"pci_devices"`
DriveDevices []*drive.Device `bson:"drive_devices" json:"drive_devices"`
CloudSubnets []*node.CloudSubnet `bson:"cloud_subnets" json:"cloud_subnets"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
}
func (i *Instance) GenerateId() (err error) {
if !i.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("instance: Instance already exists"),
}
return
}
i.newId = true
i.Id = bson.NewObjectID()
return
}
func (i *Instance) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
i.Name = utils.FilterName(i.Name)
if i.Action == "" {
i.Action = Start
}
if i.Action != Start {
i.Restart = false
i.RestartReason = ""
i.RestartBlockIp = false
}
if i.State != "" && !ValidStates.Contains(i.State) {
errData = &errortypes.ErrorData{
Error: "invalid_state",
Message: "Invalid instance state",
}
return
}
if !ValidActions.Contains(i.Action) {
errData = &errortypes.ErrorData{
Error: "invalid_action",
Message: "Invalid instance action",
}
return
}
if i.Organization.IsZero() {
errData = &errortypes.ErrorData{
Error: "organization_required",
Message: "Missing required organization",
}
return
}
if i.Zone.IsZero() {
errData = &errortypes.ErrorData{
Error: "zone_required",
Message: "Missing required zone",
}
return
}
if i.Node.IsZero() {
if i.Shape.IsZero() {
errData = &errortypes.ErrorData{
Error: "node_required",
Message: "Missing required node",
}
return
}
shpe, e := shape.Get(db, i.Shape)
if e != nil {
err = e
return
}
if !shpe.Flexible {
i.Processors = shpe.Processors
i.Memory = shpe.Memory
}
nde, e := shpe.FindNode(db, i.Processors, i.Memory)
if e != nil {
err = e
return
}
i.Node = nde.Id
i.DiskType = shpe.DiskType
i.DiskPool = shpe.DiskPool
}
if i.Vpc.IsZero() {
errData = &errortypes.ErrorData{
Error: "vpc_required",
Message: "Missing required VPC",
}
return
}
if i.UnixId == 0 {
i.GenerateUnixId()
}
switch i.DiskType {
case disk.Lvm:
if i.DiskPool.IsZero() {
errData = &errortypes.ErrorData{
Error: "pool_required",
Message: "Missing required disk pool",
}
return
}
break
case disk.Qcow2, "":
i.DiskType = disk.Qcow2
}
vc, err := vpc.Get(db, i.Vpc)
if err != nil {
return
}
if i.Subnet.IsZero() {
errData = &errortypes.ErrorData{
Error: "vpc_subnet_required",
Message: "Missing required VPC subnet",
}
return
}
sub := vc.GetSubnet(i.Subnet)
if sub == nil {
errData = &errortypes.ErrorData{
Error: "vpc_subnet_missing",
Message: "VPC subnet does not exist",
}
return
}
if i.InitDiskSize != 0 && i.InitDiskSize < 10 {
errData = &errortypes.ErrorData{
Error: "init_disk_size_invalid",
Message: "Disk size below minimum",
}
return
}
if i.Memory < 256 {
i.Memory = 256
}
if i.Processors < 1 {
i.Processors = 1
}
if i.Roles == nil {
i.Roles = []string{}
}
if i.PublicIps == nil {
i.PublicIps = []string{}
}
if i.PublicIps6 == nil {
i.PublicIps6 = []string{}
}
if i.PrivateIps == nil {
i.PrivateIps = []string{}
}
if i.PrivateIps6 == nil {
i.PrivateIps6 = []string{}
}
if i.CloudType == "" {
i.CloudType = Linux
}
if !ValidCloudTypes.Contains(i.CloudType) {
errData = &errortypes.ErrorData{
Error: "invalid_cloud_type",
Message: "Invalid cloud init type",
}
return
}
if i.SystemKind != "" && !ValidSystemKinds.Contains(i.SystemKind) {
errData = &errortypes.ErrorData{
Error: "invalid_system_kind",
Message: "Instance system kind invalid",
}
return
}
if i.CloudScript != "" && !scriptReg.MatchString(i.CloudScript) {
errData = &errortypes.ErrorData{
Error: "invalid_cloud_script",
Message: "Startup script missing shebang on first line",
}
return
}
if i.TpmSecret == "" {
i.TpmSecret, err = tpm.GenerateSecret()
if err != nil {
return
}
}
nde, err := node.Get(db, i.Node)
if err != nil {
return
}
if i.Datacenter == bson.NilObjectID {
i.Datacenter = nde.Datacenter
}
if i.Datacenter != vc.Datacenter {
errData = &errortypes.ErrorData{
Error: "vpc_invalid_datacenter",
Message: "VPC must be in same datacenter as instance",
}
return
}
if i.CloudSubnet != "" {
match := false
for _, subnet := range nde.CloudSubnets {
if subnet == i.CloudSubnet {
match = true
break
}
}
if !match {
errData = &errortypes.ErrorData{
Error: "cloud_subnet_invalid",
Message: "Invalid Cloud subnet",
}
return
}
}
if i.RootEnabled {
if i.RootPasswd == "" {
i.RootPasswd, err = utils.RandPasswd(8)
if err != nil {
return
}
}
} else {
i.RootPasswd = ""
}
if i.Isos == nil {
i.Isos = []*iso.Iso{}
} else {
for _, is := range i.Isos {
is.Name = utils.FilterRelPath(is.Name)
}
}
if i.UsbDevices == nil {
i.UsbDevices = []*usb.Device{}
} else {
for _, device := range i.UsbDevices {
device.Name = ""
device.Vendor = usb.FilterId(device.Vendor)
device.Product = usb.FilterId(device.Product)
device.Bus = usb.FilterAddr(device.Bus)
device.Address = usb.FilterAddr(device.Address)
if (device.Vendor == "" || device.Product == "") &&
(device.Bus == "" || device.Address == "") {
errData = &errortypes.ErrorData{
Error: "usb_device_invalid",
Message: "Invalid USB device",
}
return
}
available, e := usb.Available(db, i.Id, i.Node, device)
if e != nil {
err = e
return
}
if !available {
errData = &errortypes.ErrorData{
Error: "usb_device_unavailable",
Message: "USB device in use by another instance",
}
return
}
}
}
if i.PciDevices == nil {
i.PciDevices = []*pci.Device{}
} else {
for _, device := range i.PciDevices {
device.Name = ""
device.Class = ""
device.Driver = ""
if !pci.CheckSlot(device.Slot) {
errData = &errortypes.ErrorData{
Error: "pci_device_slot_invalid",
Message: "Invalid PCI slot",
}
return
}
}
}
instanceDrives := set.NewSet()
nodeInstanceDrives := nde.InstanceDrives
for _, device := range nodeInstanceDrives {
instanceDrives.Add(device.Id)
}
if i.DriveDevices == nil {
i.DriveDevices = []*drive.Device{}
} else {
for _, device := range i.DriveDevices {
if !instanceDrives.Contains(device.Id) {
errData = &errortypes.ErrorData{
Error: "drive_invalid",
Message: "Instance drive not available",
}
return
}
}
}
iscsiDevices := []*iscsi.Device{}
if i.IscsiDevices != nil {
for _, device := range i.IscsiDevices {
if device.Uri == "" {
continue
}
errData, err = device.Parse()
if err != nil || errData != nil {
return
}
iscsiDevices = append(iscsiDevices, device)
}
}
i.IscsiDevices = iscsiDevices
newMounts := []*Mount{}
for _, mount := range i.Mounts {
if mount.Name == "" && mount.HostPath == "" {
continue
}
mount.Name = utils.FilterNameCmd(mount.Name)
mount.Type = HostPath
mount.Path = utils.FilterPath(mount.Path)
mount.HostPath = utils.FilterPath(mount.HostPath)
if mount.Name == "" {
errData = &errortypes.ErrorData{
Error: "missing_mount_name",
Message: "Missing required mount name",
}
return
}
if mount.HostPath == "" {
errData = &errortypes.ErrorData{
Error: "mount_host_path_invalid",
Message: "Mount host path invalid",
}
return
}
newMounts = append(newMounts, mount)
}
i.Mounts = newMounts
if i.Vnc {
if i.VncPassword == "" {
i.VncPassword, err = utils.RandPasswd(32)
if err != nil {
return
}
}
} else {
i.VncPassword = ""
}
if i.Spice {
if i.SpicePassword == "" {
i.SpicePassword, err = utils.RandPasswd(32)
if err != nil {
return
}
}
} else {
i.SpicePassword = ""
}
externalNodePorts := set.NewSet()
for _, mapping := range i.NodePorts {
extPortKey := fmt.Sprintf("%s:%d",
mapping.Protocol, mapping.ExternalPort)
if !mapping.Delete {
if externalNodePorts.Contains(extPortKey) {
errData = &errortypes.ErrorData{
Error: "node_port_external_duplicate",
Message: "Duplicate external node port",
}
return
}
}
externalNodePorts.Add(extPortKey)
errData, err = mapping.Validate(db)
if err != nil {
return
}
available, e := nodeport.Available(db, i.Datacenter, i.Organization,
mapping.Protocol, mapping.ExternalPort)
if e != nil {
err = e
return
}
if !available {
errData = &errortypes.ErrorData{
Error: "node_port_unavailable",
Message: "External node port is unavailable",
}
return
}
}
return
}
func (i *Instance) GenerateUnixId() {
i.UnixId = rand.Intn(55500) + 10000
}
func (i *Instance) InitUnixId(db *database.Database) (err error) {
if i.UnixId != 0 {
return
}
i.GenerateUnixId()
err = i.CommitFields(db, set.NewSet("unix_id"))
if err != nil {
return
}
return
}
func (i *Instance) GenerateSpicePort() {
// Spice 15000 - 19999
i.SpicePort = rand.Intn(4999) + 15000
}
func (i *Instance) InitSpicePort(db *database.Database) (err error) {
if i.SpicePort != 0 {
return
}
i.GenerateSpicePort()
coll := db.Instances()
for n := 0; n < 10000; n++ {
err = coll.CommitFields(i.Id, i, set.NewSet("spice_port"))
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.DuplicateKeyError); ok {
i.GenerateSpicePort()
err = nil
continue
}
return
}
event.PublishDispatch(db, "instance.change")
return
}
err = &errortypes.WriteError{
errors.New("instance: Failed to commit unique spice port"),
}
return
}
func (i *Instance) GenerateVncDisplay() {
// VNC 10001 - 14999 (+5900)
// VNC WebSocket 20001 - 24999 (+15900)
i.VncDisplay = rand.Intn(4999) + 4101
}
func (i *Instance) InitVncDisplay(db *database.Database) (err error) {
if i.VncDisplay != 0 {
return
}
i.GenerateVncDisplay()
coll := db.Instances()
for n := 0; n < 10000; n++ {
err = coll.CommitFields(i.Id, i, set.NewSet("vnc_display"))
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.DuplicateKeyError); ok {
i.GenerateVncDisplay()
err = nil
continue
}
return
}
event.PublishDispatch(db, "instance.change")
return
}
err = &errortypes.WriteError{
errors.New("instance: Failed to commit unique vnc port"),
}
return
}
func (i *Instance) Format() {
}
func (i *Instance) Json(short bool) {
switch i.Action {
case Start:
if i.Restart || i.RestartBlockIp {
i.Status = "Restart Required"
if i.RestartReason != "" {
i.Status += fmt.Sprintf(" (%s)", i.RestartReason)
}
} else {
switch i.State {
case vm.Starting:
i.Status = "Starting"
break
case vm.Running:
i.Status = "Running"
break
case vm.Stopped:
i.Status = "Starting"
break
case vm.Failed:
i.Status = "Starting"
break
case vm.Updating:
i.Status = "Updating"
break
case vm.Provisioning:
i.Status = "Provisioning"
break
case "":
i.Status = "Provisioning"
break
}
}
break
case Cleanup:
switch i.State {
case vm.Starting:
i.Status = "Stopping"
break
case vm.Running:
i.Status = "Stopping"
break
case vm.Stopped:
i.Status = "Stopping"
break
case vm.Failed:
i.Status = "Stopping"
break
case vm.Updating:
i.Status = "Updating"
break
case vm.Provisioning:
i.Status = "Stopping"
break
case "":
i.Status = "Stopping"
break
}
break
case Stop:
switch i.State {
case vm.Starting:
i.Status = "Stopping"
break
case vm.Running:
i.Status = "Stopping"
break
case vm.Stopped:
i.Status = "Stopped"
break
case vm.Failed:
i.Status = "Failed"
break
case vm.Updating:
i.Status = "Updating"
break
case vm.Provisioning:
i.Status = "Stopped"
break
case "":
i.Status = "Stopped"
break
}
break
case Restart:
i.Status = "Restarting"
break
case Destroy:
i.Status = "Destroying"
break
}
if !i.IsActive() && i.Guest != nil {
i.Guest.Timestamp = time.Time{}
i.Guest.Heartbeat = time.Time{}
i.Guest.Memory = 0
i.Guest.HugePages = 0
i.Guest.Load1 = 0
i.Guest.Load5 = 0
i.Guest.Load15 = 0
i.Guest.Updates = []*telemetry.Update{}
}
i.PublicMac = vm.GetMacAddrExternal(i.Id, i.Vpc)
if i.Timestamp.IsZero() || !i.IsActive() {
i.Uptime = ""
} else {
if short {
i.Uptime = systemd.FormatUptimeShort(i.Timestamp)
} else {
i.Uptime = systemd.FormatUptime(i.Timestamp)
}
}
if i.IscsiDevices != nil {
for _, device := range i.IscsiDevices {
device.Json()
}
}
}
func (i *Instance) IsActive() bool {
return i.Action == Start || i.State == vm.Running ||
i.State == vm.Starting || i.State == vm.Provisioning
}
func (i *Instance) IsIpv6Only() bool {
return (node.Self.NetworkMode == node.Disabled || i.NoPublicAddress) &&
(node.Self.NetworkMode6 != node.Disabled && !i.NoPublicAddress6) &&
(node.Self.NoHostNetwork || i.NoHostAddress)
}
func (i *Instance) PreCommit() {
i.curVpc = i.Vpc
i.curSubnet = i.Subnet
i.curDeleteProtection = i.DeleteProtection
i.curAction = i.Action
i.curNoPublicAddress = i.NoPublicAddress
i.curNoHostAddress = i.NoHostAddress
nodePortMap := map[bson.ObjectID]*nodeport.Mapping{}
for _, mapping := range i.NodePorts {
nodePortMap[mapping.NodePort] = mapping
}
i.curNodePorts = nodePortMap
}
func (i *Instance) UpsertNodePorts(newNodePorts []*nodeport.Mapping) {
if len(i.NodePorts) == 0 {
i.NodePorts = newNodePorts
return
}
processed := make(map[int]bool)
newMappings := []*nodeport.Mapping{}
for _, newMapping := range newNodePorts {
matched := false
if newMapping.ExternalPort != 0 {
for x, curMapping := range i.NodePorts {
if curMapping.Protocol == newMapping.Protocol &&
curMapping.InternalPort == newMapping.InternalPort &&
curMapping.ExternalPort == newMapping.ExternalPort {
newMapping.NodePort = curMapping.NodePort
newMappings = append(newMappings, newMapping)
processed[x] = true
matched = true
break
}
}
} else {
for x, curMapping := range i.NodePorts {
if curMapping.Protocol == newMapping.Protocol &&
curMapping.InternalPort == newMapping.InternalPort &&
!processed[x] {
newMapping.NodePort = curMapping.NodePort
newMapping.ExternalPort = curMapping.ExternalPort
newMappings = append(newMappings, newMapping)
processed[x] = true
matched = true
break
}
}
}
if !matched {
newMappings = append(newMappings, newMapping)
}
}
i.NodePorts = newMappings
}
func (i *Instance) SyncNodePorts(db *database.Database) (err error) {
newNodePorts := []*nodeport.Mapping{}
newNodePortIds := set.NewSet()
externalPorts := set.NewSet()
if i.curNodePorts == nil {
i.curNodePorts = map[bson.ObjectID]*nodeport.Mapping{}
}
for _, mapping := range i.NodePorts {
if !mapping.NodePort.IsZero() {
curMapping := i.curNodePorts[mapping.NodePort]
if curMapping == nil {
continue
}
newNodePortIds.Add(curMapping.NodePort)
if mapping.Delete {
i.removedNodePorts = append(
i.removedNodePorts, curMapping.NodePort)
continue
}
curMapping.InternalPort = mapping.InternalPort
mapping = curMapping
}
var errData *errortypes.ErrorData
var ndePort *nodeport.NodePort
if mapping.ExternalPort != 0 {
ndePort, err = nodeport.GetPort(db, i.Datacenter, i.Organization,
mapping.Protocol, mapping.ExternalPort)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
ndePort = nil
err = nil
} else {
return
}
}
}
if ndePort == nil {
ndePort, errData, err = nodeport.New(db,
i.Datacenter, i.Organization,
mapping.Protocol, mapping.ExternalPort)
if err != nil {
return
}
if errData != nil {
err = errData.GetError()
return
}
}
mapping.NodePort = ndePort.Id
mapping.ExternalPort = ndePort.Port
extPortKey := fmt.Sprintf("%s:%d",
mapping.Protocol, mapping.ExternalPort)
if externalPorts.Contains(extPortKey) {
continue
}
externalPorts.Add(extPortKey)
newNodePorts = append(newNodePorts, mapping)
}
i.NodePorts = newNodePorts
for _, mapping := range i.curNodePorts {
if newNodePortIds.Contains(mapping.NodePort) {
continue
}
i.removedNodePorts = append(i.removedNodePorts, mapping.NodePort)
}
return
}
func (i *Instance) PostCommit(db *database.Database) (
dskChange bool, err error) {
err = i.SyncNodePorts(db)
if err != nil {
return
}
if (!i.curVpc.IsZero() && i.curVpc != i.Vpc) ||
(!i.curSubnet.IsZero() && i.curSubnet != i.Subnet) {
i.DhcpIp = ""
i.DhcpIp6 = ""
err = vpc.RemoveInstanceIp(db, i.Id, i.curVpc)
if err != nil {
return
}
}
if i.curDeleteProtection != i.DeleteProtection {
dskChange = true
err = disk.SetDeleteProtection(db, i.Id, i.DeleteProtection)
if err != nil {
return
}
}
if i.curAction != i.Action && (i.Action == Stop || i.Action == Start ||
i.Action == Restart) {
i.Restart = false
i.RestartBlockIp = false
}
if i.curNoPublicAddress != i.NoPublicAddress && i.NoPublicAddress {
err = block.RemoveInstanceIpsType(db, i.Id, block.External)
if err != nil {
return
}
}
if i.curNoHostAddress != i.NoHostAddress && i.NoHostAddress {
err = block.RemoveInstanceIpsType(db, i.Id, block.Host)
if err != nil {
return
}
}
return
}
func (i *Instance) Cleanup(db *database.Database) (err error) {
for _, mapping := range i.NodePorts {
ndePort, e := nodeport.Get(db, mapping.NodePort)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
err = ndePort.Sync(db)
if err != nil {
return
}
}
for _, ndePortId := range i.removedNodePorts {
ndePort, e := nodeport.Get(db, ndePortId)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
err = ndePort.Sync(db)
if err != nil {
return
}
}
return
}
func (i *Instance) Commit(db *database.Database) (err error) {
coll := db.Instances()
err = coll.Commit(i.Id, i)
if err != nil {
return
}
return
}
func (i *Instance) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Instances()
if fields.Contains("unix_id") {
for n := 0; n < 10000; n++ {
err = coll.CommitFields(i.Id, i, fields)
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.DuplicateKeyError); ok {
i.GenerateUnixId()
err = nil
continue
}
return
}
return
}
err = &errortypes.WriteError{
errors.New("instance: Failed to commit unique unix id"),
}
return
} else {
err = coll.CommitFields(i.Id, i, fields)
if err != nil {
return
}
}
return
}
func (i *Instance) Insert(db *database.Database) (err error) {
coll := db.Instances()
if !i.Id.IsZero() && !i.newId {
err = &errortypes.DatabaseError{
errors.New("instance: Instance already exists"),
}
return
}
i.Created = time.Now()
for n := 0; n < 2000; n++ {
resp, e := coll.InsertOne(db, i)
if e != nil {
err = database.ParseError(e)
if _, ok := err.(*database.DuplicateKeyError); ok {
i.GenerateUnixId()
err = nil
continue
}
return
}
i.Id = resp.InsertedID.(bson.ObjectID)
return
}
err = &errortypes.WriteError{
errors.New("instance: Failed to insert unique unix id"),
}
return
}
func (i *Instance) LoadVirt(poolsMap map[bson.ObjectID]*pool.Pool,
disks []*disk.Disk) {
i.Virt = &vm.VirtualMachine{
Id: i.Id,
Organization: i.Organization,
UnixId: i.UnixId,
DiskType: i.DiskType,
DiskPool: i.DiskPool,
Image: i.Image,
Processors: i.Processors,
Memory: i.Memory,
Hugepages: node.Self.Hugepages,
Vnc: i.Vnc,
VncDisplay: i.VncDisplay,
Spice: i.Spice,
SpicePort: i.SpicePort,
Gui: i.Gui,
Disks: []*vm.Disk{},
NetworkAdapters: []*vm.NetworkAdapter{
&vm.NetworkAdapter{
Type: vm.Bridge,
MacAddress: vm.GetMacAddr(i.Id, i.Vpc),
Vpc: i.Vpc,
Subnet: i.Subnet,
},
},
CloudSubnet: i.CloudSubnet,
CloudVnic: i.CloudVnic,
CloudVnicAttach: i.CloudVnicAttach,
DhcpIp: i.DhcpIp,
DhcpIp6: i.DhcpIp6,
Uefi: i.Uefi,
SecureBoot: i.SecureBoot,
Tpm: i.Tpm,
DhcpServer: i.DhcpServer,
Deployment: i.Deployment,
CloudType: i.CloudType,
SystemKind: i.SystemKind,
NoPublicAddress: i.NoPublicAddress,
NoPublicAddress6: i.NoPublicAddress6,
NoHostAddress: i.NoHostAddress,
Isos: []*vm.Iso{},
UsbDevices: []*vm.UsbDevice{},
PciDevices: []*vm.PciDevice{},
DriveDevices: []*vm.DriveDevice{},
IscsiDevices: []*vm.IscsiDevice{},
Mounts: []*vm.Mount{},
}
if disks != nil {
for _, dsk := range disks {
switch dsk.Type {
case disk.Lvm:
if poolsMap == nil {
continue
}
pl := poolsMap[dsk.Pool]
if pl == nil {
continue
}
i.Virt.DriveDevices = append(
i.Virt.DriveDevices,
&vm.DriveDevice{
Id: dsk.Id.Hex(),
Type: vm.Lvm,
VgName: pl.VgName,
LvName: dsk.Id.Hex(),
},
)
break
case disk.Qcow2, "":
index, err := strconv.Atoi(dsk.Index)
if err != nil {
continue
}
i.Virt.Disks = append(i.Virt.Disks, &vm.Disk{
Id: dsk.Id,
Index: index,
Path: paths.GetDiskPath(dsk.Id),
})
break
}
}
}
for _, is := range i.Isos {
i.Virt.Isos = append(i.Virt.Isos, &vm.Iso{
Name: is.Name,
})
}
if node.Self.UsbPassthrough && i.UsbDevices != nil {
for _, device := range i.UsbDevices {
usbDevice, _ := usb.GetDevice(
device.Bus, device.Address,
device.Vendor, device.Product,
)
if usbDevice != nil {
i.Virt.UsbDevices = append(i.Virt.UsbDevices, &vm.UsbDevice{
Vendor: usbDevice.Vendor,
Product: usbDevice.Product,
Bus: usbDevice.Bus,
Address: usbDevice.Address,
})
}
}
}
if node.Self.PciPassthrough && i.PciDevices != nil {
for _, device := range i.PciDevices {
i.Virt.PciDevices = append(i.Virt.PciDevices, &vm.PciDevice{
Slot: device.Slot,
})
}
}
instanceDrives := set.NewSet()
nodeInstanceDrives := node.Self.InstanceDrives
if nodeInstanceDrives != nil {
for _, device := range nodeInstanceDrives {
instanceDrives.Add(device.Id)
}
}
if i.DriveDevices != nil {
for _, device := range i.DriveDevices {
if instanceDrives.Contains(device.Id) {
i.Virt.DriveDevices = append(
i.Virt.DriveDevices,
&vm.DriveDevice{
Id: device.Id,
Type: vm.Physical,
},
)
}
}
}
if node.Self.Iscsi && i.IscsiDevices != nil {
for _, device := range i.IscsiDevices {
i.Virt.IscsiDevices = append(
i.Virt.IscsiDevices,
&vm.IscsiDevice{
Uri: device.QemuUri(),
},
)
}
}
for _, mount := range i.Mounts {
i.Virt.Mounts = append(
i.Virt.Mounts,
&vm.Mount{
Name: mount.Name,
Type: mount.Type,
Path: mount.Path,
HostPath: mount.HostPath,
},
)
}
return
}
func (i *Instance) Changed(curVirt *vm.VirtualMachine) (bool, string) {
curCloudType := curVirt.CloudType
if curCloudType == "" {
curCloudType = Linux
}
cloudType := i.Virt.CloudType
if cloudType == "" {
cloudType = Linux
}
if i.Virt.Memory != curVirt.Memory {
return true, "Memory size changed"
}
if i.Virt.Hugepages != curVirt.Hugepages {
return true, "Hugepages changed"
}
if i.Virt.Processors != curVirt.Processors {
return true, "Processor count changed"
}
if i.Virt.Vnc != curVirt.Vnc {
return true, "VNC changed"
}
if i.Virt.VncDisplay != curVirt.VncDisplay {
return true, "VNC display changed"
}
if i.Virt.Spice != curVirt.Spice {
return true, "SPICE changed"
}
if i.Virt.SpicePort != curVirt.SpicePort {
return true, "SPICE port changed"
}
if i.Virt.Gui != curVirt.Gui {
return true, "GUI changed"
}
if i.Virt.Uefi != curVirt.Uefi {
return true, "UEFI changed"
}
if i.Virt.SecureBoot != curVirt.SecureBoot {
return true, "Secure boot changed"
}
if i.Virt.Tpm != curVirt.Tpm {
return true, "TPM changed"
}
if i.Virt.DhcpServer != curVirt.DhcpServer {
return true, "DHCP server changed"
}
if cloudType != curCloudType {
return true, "Cloud type changed"
}
if i.Virt.NoPublicAddress != curVirt.NoPublicAddress {
return true, "Public address changed"
}
if i.Virt.NoPublicAddress6 != curVirt.NoPublicAddress6 {
return true, "Public IPv6 changed"
}
if i.Virt.NoHostAddress != curVirt.NoHostAddress {
return true, "Host address changed"
}
for i, adapter := range i.Virt.NetworkAdapters {
if len(curVirt.NetworkAdapters) <= i {
return true, "Network adapters changed"
}
if adapter.Vpc != curVirt.NetworkAdapters[i].Vpc {
return true, "VPC changed"
}
if adapter.Subnet != curVirt.NetworkAdapters[i].Subnet {
return true, "Subnet changed"
}
}
if i.Virt.Isos != nil {
if len(i.Virt.Isos) > 0 && curVirt.Isos == nil {
return true, "ISO devices changed"
}
for i, device := range i.Virt.Isos {
if len(curVirt.Isos) <= i {
return true, "ISO devices changed"
}
if device.Name != curVirt.Isos[i].Name {
return true, "ISO device changed"
}
}
}
if i.Virt.PciDevices != nil {
if len(i.Virt.PciDevices) > 0 && curVirt.PciDevices == nil {
return true, "PCI devices changed"
}
for i, device := range i.Virt.PciDevices {
if len(curVirt.PciDevices) <= i {
return true, "PCI devices changed"
}
if device.Slot != curVirt.PciDevices[i].Slot {
return true, "PCI device slot changed"
}
}
}
if i.Virt.DriveDevices != nil {
if len(i.Virt.DriveDevices) > 0 && curVirt.DriveDevices == nil {
return true, "Drive devices changed"
}
for i, device := range i.Virt.DriveDevices {
if len(curVirt.DriveDevices) <= i {
return true, "Drive devices changed"
}
if device.Id != curVirt.DriveDevices[i].Id {
return true, "Drive device changed"
}
}
}
if i.Virt.IscsiDevices != nil {
if len(i.Virt.IscsiDevices) > 0 && curVirt.IscsiDevices == nil {
return true, "iSCSI devices changed"
}
for i, device := range i.Virt.IscsiDevices {
if len(curVirt.IscsiDevices) <= i {
return true, "iSCSI devices changed"
}
if device.Uri != curVirt.IscsiDevices[i].Uri {
return true, "iSCSI URI changed"
}
}
}
if i.Virt.Mounts != nil {
if len(i.Virt.Mounts) > 0 && curVirt.Mounts == nil {
return true, "Mounts changed"
}
for i, mount := range i.Virt.Mounts {
if len(curVirt.Mounts) <= i {
return true, "Mounts changed"
}
if mount.Name != curVirt.Mounts[i].Name {
return true, "Mount name changed"
}
if mount.Type != curVirt.Mounts[i].Type {
return true, "Mount type changed"
}
if mount.Path != curVirt.Mounts[i].Path {
return true, "Mount path changed"
}
if mount.HostPath != curVirt.Mounts[i].HostPath {
return true, "Mount host path changed"
}
}
}
return false, ""
}
func (i *Instance) DiskChanged(curVirt *vm.VirtualMachine) (
addDisks, remDisks []*vm.Disk) {
addDisks = []*vm.Disk{}
remDisks = []*vm.Disk{}
if !curVirt.DisksAvailable {
logrus.WithFields(logrus.Fields{
"instance_id": curVirt.Id.Hex(),
}).Warn("qemu: Ignoring disk state")
return
}
disks := map[bson.ObjectID]*vm.Disk{}
curDisks := set.NewSet()
for _, dsk := range i.Virt.Disks {
disks[dsk.Id] = dsk
}
for _, dsk := range curVirt.Disks {
newDsk := disks[dsk.Id]
if newDsk == nil || dsk.Index != newDsk.Index {
remDisks = append(remDisks, dsk)
} else {
curDisks.Add(dsk.Id)
}
}
for _, dsk := range i.Virt.Disks {
if !curDisks.Contains(dsk.Id) {
addDisks = append(addDisks, dsk)
}
}
return
}
func (i *Instance) UsbChanged(curVirt *vm.VirtualMachine) (
addUsbs, remUsbs []*vm.UsbDevice) {
addUsbs = []*vm.UsbDevice{}
remUsbs = []*vm.UsbDevice{}
if !node.Self.UsbPassthrough {
return
}
if !curVirt.UsbDevicesAvailable {
logrus.WithFields(logrus.Fields{
"instance_id": curVirt.Id.Hex(),
}).Warn("qemu: Ignoring USB state")
return
}
usbs := set.NewSet()
usbsMap := map[string]*vm.UsbDevice{}
curUsbs := set.NewSet()
curUsbsMap := map[string]*vm.UsbDevice{}
if curVirt.UsbDevices != nil {
for _, device := range curVirt.UsbDevices {
key := device.Key()
curUsbs.Add(key)
curUsbsMap[key] = device
}
}
if i.Virt.UsbDevices != nil {
for _, device := range i.Virt.UsbDevices {
key := device.Key()
usbs.Add(key)
usbsMap[key] = device
}
}
addUsbsSet := usbs.Copy()
addUsbsSet.Subtract(curUsbs)
remUsbsSet := curUsbs.Copy()
remUsbsSet.Subtract(usbs)
for deviceInf := range addUsbsSet.Iter() {
device := usbsMap[deviceInf.(string)]
addUsbs = append(addUsbs, device)
}
for deviceInf := range remUsbsSet.Iter() {
device := curUsbsMap[deviceInf.(string)]
remUsbs = append(remUsbs, device)
}
return
}
func (i *Instance) VncConnect(db *database.Database,
rw http.ResponseWriter, r *http.Request) (err error) {
nde, err := node.Get(db, i.Node)
if err != nil {
return
}
vncHost := ""
if nde.Id == node.Self.Id {
vncHost = "127.0.0.1"
}
if vncHost == "" && len(nde.PrivateIps) > 0 {
vncHost = nde.PrivateIps[nde.DefaultInterface]
if vncHost == "" {
for _, privIp := range nde.PrivateIps {
if privIp != "" {
vncHost = privIp
break
}
}
}
}
if vncHost == "" && len(nde.PublicIps) > 0 {
vncHost = nde.PublicIps[0]
}
if vncHost == "" {
err = &errortypes.NotFoundError{
errors.New("instance: Node missing IP for VNC"),
}
return
}
wsUrl := fmt.Sprintf(
"ws://%s:%d",
vncHost,
i.VncDisplay+15900,
)
var backConn *websocket.Conn
var backResp *http.Response
dialer := &websocket.Dialer{
HandshakeTimeout: 10 * time.Second,
}
header := http.Header{}
header.Set(
"Sec-Websocket-Protocol",
r.Header.Get("Sec-Websocket-Protocol"),
)
backConn, backResp, err = dialer.Dial(wsUrl, header)
if err != nil {
if backResp != nil {
err = &VncDialError{
errors.Wrapf(err, "instance: WebSocket dial error %d",
backResp.StatusCode),
}
} else {
err = &VncDialError{
errors.Wrap(err, "instance: WebSocket dial error"),
}
}
return
}
defer backConn.Close()
wsUpgrader := &websocket.Upgrader{
HandshakeTimeout: time.Duration(
settings.Router.HandshakeTimeout) * time.Second,
ReadBufferSize: 2048,
WriteBufferSize: 2048,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
upgradeHeader := http.Header{}
val := backResp.Header.Get("Sec-Websocket-Protocol")
if val != "" {
upgradeHeader.Set("Sec-Websocket-Protocol", val)
}
frontConn, err := wsUpgrader.Upgrade(rw, r, upgradeHeader)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "instance: WebSocket upgrade error"),
}
return
}
defer frontConn.Close()
wait := make(chan bool, 4)
go func() {
defer func() {
rec := recover()
if rec != nil {
logrus.WithFields(logrus.Fields{
"panic": rec,
}).Error("instance: WebSocket VNC back panic")
wait <- true
}
}()
for {
msgType, msg, err := frontConn.ReadMessage()
if err != nil {
closeMsg := websocket.FormatCloseMessage(
websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
closeMsg = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
_ = backConn.WriteMessage(websocket.CloseMessage, closeMsg)
break
}
err = backConn.WriteMessage(msgType, msg)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "instance: WebSocket VNC write error"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("instance: WebSocket VNC back write error")
break
}
}
wait <- true
}()
go func() {
defer func() {
rec := recover()
if rec != nil {
logrus.WithFields(logrus.Fields{
"panic": rec,
}).Error("instance: WebSocket VNC front panic")
wait <- true
}
}()
for {
msgType, msg, err := backConn.ReadMessage()
if err != nil {
closeMsg := websocket.FormatCloseMessage(
websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
closeMsg = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
_ = frontConn.WriteMessage(websocket.CloseMessage, closeMsg)
break
}
err = frontConn.WriteMessage(msgType, msg)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "instance: WebSocket VNC write error"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("instance: WebSocket VNC back write error")
break
}
}
wait <- true
}()
<-wait
return
}
================================================
FILE: instance/utils.go
================================================
package instance
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/journal"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
func Get(db *database.Database, instId bson.ObjectID) (
inst *Instance, err error) {
coll := db.Instances()
inst = &Instance{}
err = coll.FindOneId(instId, inst)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, instId bson.ObjectID) (
inst *Instance, err error) {
coll := db.Instances()
inst = &Instance{}
err = coll.FindOne(db, &bson.M{
"_id": instId,
"organization": orgId,
}).Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (inst *Instance, err error) {
coll := db.Instances()
inst = &Instance{}
err = coll.FindOne(db, query).Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func ExistsIp(db *database.Database, addr string) (exists bool, err error) {
coll := db.Instances()
n, err := coll.CountDocuments(db, &bson.M{
"$or": []bson.M{
{"public_ips": addr},
{"public_ips6": addr},
{"cloud_private_ips": addr},
{"cloud_public_ips": addr},
{"cloud_public_ips6": addr},
{"host_ips": addr},
{"node_port_ips": addr},
},
})
if err != nil {
err = database.ParseError(err)
return
}
if n > 0 {
exists = true
}
return
}
func ExistsOrg(db *database.Database, orgId, instId bson.ObjectID) (
exists bool, err error) {
coll := db.Instances()
n, err := coll.CountDocuments(db, &bson.M{
"_id": instId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
if n > 0 {
exists = true
}
return
}
func GetAll(db *database.Database, query *bson.M) (
insts []*Instance, err error) {
coll := db.Instances()
insts = []*Instance{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
insts = append(insts, inst)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllRoles(db *database.Database, query *bson.M) (
insts []*Instance, rolesSet set.Set, err error) {
coll := db.Instances()
insts = []*Instance{}
rolesSet = set.NewSet()
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
insts = append(insts, inst)
for _, role := range inst.Roles {
rolesSet.Add(role)
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllVirt(db *database.Database, query *bson.M,
pools []*pool.Pool, disks []*disk.Disk) (
insts []*Instance, err error) {
poolsMap := map[bson.ObjectID]*pool.Pool{}
for _, pl := range pools {
poolsMap[pl.Id] = pl
}
instanceDisks := map[bson.ObjectID][]*disk.Disk{}
if disks != nil {
for _, dsk := range disks {
if dsk.Action == disk.Destroy {
if dsk.DeleteProtection {
logrus.WithFields(logrus.Fields{
"disk_id": dsk.Id.Hex(),
}).Info("instance: Delete protection ignore disk detach")
} else {
continue
}
} else if dsk.State != disk.Available &&
dsk.State != disk.Attached {
continue
}
dsks := instanceDisks[dsk.Instance]
if dsks == nil {
dsks = []*disk.Disk{}
}
instanceDisks[dsk.Instance] = append(dsks, dsk)
}
}
coll := db.Instances()
insts = []*Instance{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
inst.LoadVirt(poolsMap, instanceDisks[inst.Id])
insts = append(insts, inst)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllVirtMapped(db *database.Database, query *bson.M,
pools []*pool.Pool, instanceDisks map[bson.ObjectID][]*disk.Disk) (
insts []*Instance, err error) {
coll := db.Instances()
insts = []*Instance{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
poolsMap := map[bson.ObjectID]*pool.Pool{}
for _, pl := range pools {
poolsMap[pl.Id] = pl
}
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
virtDsks := []*disk.Disk{}
dsks := instanceDisks[inst.Id]
if dsks != nil {
for _, dsk := range dsks {
if dsk.Action == disk.Destroy {
if dsk.DeleteProtection {
logrus.WithFields(logrus.Fields{
"disk_id": dsk.Id.Hex(),
}).Info("instance: Delete protection ignore disk detach")
} else {
continue
}
} else if dsk.State != disk.Available &&
dsk.State != disk.Attached {
continue
}
virtDsks = append(virtDsks, dsk)
}
}
inst.LoadVirt(poolsMap, virtDsks)
insts = append(insts, inst)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func LoadAllVirt(insts []*Instance, pools []*pool.Pool,
instanceDisks map[bson.ObjectID][]*disk.Disk) []*Instance {
poolsMap := map[bson.ObjectID]*pool.Pool{}
for _, pl := range pools {
poolsMap[pl.Id] = pl
}
for _, inst := range insts {
virtDsks := []*disk.Disk{}
dsks := instanceDisks[inst.Id]
for _, dsk := range dsks {
if dsk.Action == disk.Destroy {
if dsk.DeleteProtection {
logrus.WithFields(logrus.Fields{
"disk_id": dsk.Id.Hex(),
}).Info("instance: Delete protection ignore disk detach")
} else {
continue
}
} else if dsk.State != disk.Available &&
dsk.State != disk.Attached {
continue
}
virtDsks = append(virtDsks, dsk)
}
inst.LoadVirt(poolsMap, virtDsks)
}
return insts
}
func GetAllName(db *database.Database, query *bson.M) (
instances []*Instance, err error) {
coll := db.Instances()
instances = []*Instance{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetProjection(&bson.D{
{"name", 1},
}),
)
defer cursor.Close(db)
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
instances = append(instances, inst)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (insts []*Instance, count int64, err error) {
coll := db.Instances()
insts = []*Instance{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
insts = append(insts, inst)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, instId bson.ObjectID) (err error) {
inst, err := Get(db, instId)
if err != nil {
return
}
if inst.DeleteProtection {
logrus.WithFields(logrus.Fields{
"instance_id": instId.Hex(),
}).Info("instance: Delete protection ignore instance remove")
return
}
err = block.RemoveInstanceIps(db, instId)
if err != nil {
return
}
err = vpc.RemoveInstanceIps(db, instId)
if err != nil {
return
}
err = journal.RemoveAll(db, instId)
if err != nil {
return
}
coll := db.Instances()
_, err = coll.DeleteOne(db, &bson.M{
"_id": instId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
_ = inst.Cleanup(db)
return
}
func Delete(db *database.Database, instId bson.ObjectID) (err error) {
coll := db.Instances()
err = coll.UpdateId(instId, &bson.M{
"$set": &bson.M{
"action": Destroy,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func DeleteOrg(db *database.Database, orgId, instId bson.ObjectID) (
err error) {
coll := db.Instances()
err = coll.UpdateId(instId, &bson.M{
"$set": &bson.M{
"action": Destroy,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func DeleteMulti(db *database.Database, instIds []bson.ObjectID) (
err error) {
coll := db.Instances()
_, err = coll.UpdateMany(db, &bson.M{
"_id": &bson.M{
"$in": instIds,
},
"delete_protection": &bson.M{
"$ne": true,
},
}, &bson.M{
"$set": &bson.M{
"action": Destroy,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func DeleteMultiOrg(db *database.Database, orgId bson.ObjectID,
instIds []bson.ObjectID) (err error) {
coll := db.Instances()
_, err = coll.UpdateMany(db, &bson.M{
"_id": &bson.M{
"$in": instIds,
},
"organization": orgId,
"delete_protection": &bson.M{
"$ne": true,
},
}, &bson.M{
"$set": &bson.M{
"action": Destroy,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func UpdateMulti(db *database.Database, instIds []bson.ObjectID,
doc *bson.M) (err error) {
coll := db.Instances()
query := &bson.M{
"_id": &bson.M{
"$in": instIds,
},
"action": &bson.M{
"$ne": Destroy,
},
}
if (*doc)["action"] == Destroy {
(*query)["delete_protection"] = &bson.M{
"$ne": true,
}
}
_, err = coll.UpdateMany(db, query, &bson.M{
"$set": doc,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func UpdateMultiOrg(db *database.Database, orgId bson.ObjectID,
instIds []bson.ObjectID, doc *bson.M) (err error) {
coll := db.Instances()
query := &bson.M{
"_id": &bson.M{
"$in": instIds,
},
"organization": orgId,
"action": &bson.M{
"$ne": Destroy,
},
}
if (*doc)["action"] == Destroy {
(*query)["delete_protection"] = &bson.M{
"$ne": true,
}
}
_, err = coll.UpdateMany(db, query, &bson.M{
"$set": doc,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func SetAction(db *database.Database, instId bson.ObjectID,
action string) (err error) {
coll := db.Instances()
_, err = coll.UpdateOne(db, &bson.M{
"_id": instId,
"action": &bson.M{
"$ne": Destroy,
},
}, &bson.M{
"$set": &bson.M{
"action": action,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func SetDownloadProgress(db *database.Database,
instId bson.ObjectID, progress int, speedMb float64) (err error) {
coll := db.Instances()
_, err = coll.UpdateOne(db, &bson.M{
"_id": instId,
}, &bson.M{
"$set": &bson.M{
"status_info.download_progress": progress,
"status_info.download_speed": speedMb,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: interfaces/interfaces.go
================================================
package interfaces
import (
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
var (
curVxlan = false
ifaces = map[string]set.Set{}
ifacesLock = sync.Mutex{}
lastChange time.Time
)
func getIfaces(bridge string) (ifacesSet set.Set, err error) {
ifacesSet = set.NewSet()
ifaces, err := iproute.IfaceGetBridgeIfaces("", bridge)
if err != nil {
return
}
for _, iface := range ifaces {
ifacesSet.Add(iface.Name)
}
return
}
func SyncIfaces(vxlan bool) {
nde := node.Self
if vxlan == curVxlan && time.Since(lastChange) < 10*time.Second {
return
}
curVxlan = vxlan
ifacesNew := map[string]set.Set{}
externalIfaces := nde.ExternalInterfaces
internalIfaces := nde.InternalInterfaces
blocks := nde.Blocks
for _, iface := range externalIfaces {
ifaceSet, err := getIfaces(iface)
if err != nil {
logrus.WithFields(logrus.Fields{
"bridge": iface,
"error": err,
}).Error("interfaces: Bridge ifaces get failed")
} else {
ifacesNew[iface] = ifaceSet
}
}
for _, iface := range internalIfaces {
ifaceSet, err := getIfaces(iface)
if err != nil {
logrus.WithFields(logrus.Fields{
"bridge": iface,
"error": err,
}).Error("interfaces: Bridge ifaces get failed")
} else {
ifacesNew[iface] = ifaceSet
}
brIface := vm.GetHostBridgeIface(iface)
ifaceSet, err = getIfaces(brIface)
if err != nil {
logrus.WithFields(logrus.Fields{
"bridge": brIface,
"error": err,
}).Error("interfaces: Bridge ifaces get failed")
} else {
ifacesNew[brIface] = ifaceSet
}
}
for _, blck := range blocks {
if blck.Interface == "" {
continue
}
ifaceSet, err := getIfaces(blck.Interface)
if err != nil {
logrus.WithFields(logrus.Fields{
"bridge": blck.Interface,
"error": err,
}).Error("interfaces: Bridge ifaces get failed")
} else {
ifacesNew[blck.Interface] = ifaceSet
}
}
ifacesLock.Lock()
lastChange = time.Now()
ifaces = ifacesNew
ifacesLock.Unlock()
return
}
func GetExternal(virtIface string) (externalIface string) {
externalIfaces := node.Self.ExternalInterfaces
if externalIfaces != nil {
curLen := 0
for _, iface := range externalIfaces {
ifacesSetLen := 0
ifacesLock.Lock()
ifacesSet := ifaces[iface]
if ifacesSet != nil {
ifacesSetLen = ifacesSet.Len()
}
ifacesLock.Unlock()
if ifacesSet == nil {
continue
}
if externalIface == "" || ifacesSetLen < curLen {
curLen = ifacesSetLen
externalIface = iface
}
}
if externalIface != "" {
ifacesLock.Lock()
lastChange = time.Now()
ifacesSet := ifaces[externalIface]
if ifacesSet != nil {
ifacesSet.Add(virtIface)
}
ifacesLock.Unlock()
}
}
return
}
func HasExternal() (exists bool) {
externalIfaces := node.Self.ExternalInterfaces
externalIface := ""
if len(externalIfaces) > 0 {
externalIface = externalIfaces[0]
}
if externalIface != "" {
exists = true
}
return
}
func GetInternal(virtIface string, vxlan bool) (internalIface string) {
internalIfaces := node.Self.InternalInterfaces
if internalIfaces != nil {
curLen := 0
for _, iface := range internalIfaces {
if vxlan {
iface = vm.GetHostBridgeIface(iface)
}
ifacesSetLen := 0
ifacesLock.Lock()
ifacesSet := ifaces[iface]
if ifacesSet != nil {
ifacesSetLen = ifacesSet.Len()
}
ifacesLock.Unlock()
if ifacesSet == nil {
continue
}
if internalIface == "" || ifacesSetLen < curLen {
curLen = ifacesSetLen
internalIface = iface
}
}
if internalIface != "" {
ifacesLock.Lock()
lastChange = time.Now()
ifacesSet := ifaces[internalIface]
if ifacesSet != nil {
ifacesSet.Add(virtIface)
}
ifacesLock.Unlock()
}
}
return
}
func GetBridges(nde *node.Node) (bridges set.Set) {
bridges = set.NewSet()
externalIfaces := nde.ExternalInterfaces
for _, iface := range externalIfaces {
bridges.Add(iface)
}
internalIfaces := nde.InternalInterfaces
for _, iface := range internalIfaces {
bridges.Add(iface)
}
ndeBlocks := nde.Blocks
for _, blck := range ndeBlocks {
if blck.Interface == "" {
continue
}
bridges.Add(blck.Interface)
}
ndeBlocks6 := nde.Blocks6
for _, blck := range ndeBlocks6 {
if blck.Interface == "" {
continue
}
bridges.Add(blck.Interface)
}
return
}
func GetBridgesInternal(nde *node.Node) (bridges set.Set) {
bridges = set.NewSet()
internalIfaces := nde.InternalInterfaces
for _, iface := range internalIfaces {
bridges.Add(iface)
}
return
}
func GetBridgesExternal(nde *node.Node) (bridges set.Set) {
bridges = set.NewSet()
externalIfaces := nde.ExternalInterfaces
for _, iface := range externalIfaces {
bridges.Add(iface)
}
ndeBlocks := nde.Blocks
for _, blck := range ndeBlocks {
if blck.Interface == "" {
continue
}
bridges.Add(blck.Interface)
}
ndeBlocks6 := nde.Blocks6
for _, blck := range ndeBlocks6 {
if blck.Interface == "" {
continue
}
bridges.Add(blck.Interface)
}
return
}
func RemoveVirtIface(virtIface string) {
if virtIface == "" {
return
}
ifacesLock.Lock()
lastChange = time.Now()
for iface, ifaceSet := range ifaces {
ifaceSet.Remove(virtIface)
ifaces[iface] = ifaceSet
}
ifacesLock.Unlock()
}
================================================
FILE: ip/interface.go
================================================
package ip
type Interface struct {
Name string `bson:"name" json:"name"`
Address string `bson:"address" json:"address"`
}
================================================
FILE: ip/ip.go
================================================
package ip
import (
"encoding/json"
"net"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
cache = map[string]map[string]*Iface{}
cacheTime = map[string]time.Time{}
cacheLock = sync.Mutex{}
)
type Iface struct {
Ifindex int `json:"ifindex"`
Ifname string `json:"ifname"`
Flags []string `json:"flags"`
Mtu int `json:"mtu"`
Qdisc string `json:"qdisc"`
Operstate string `json:"operstate"`
Group string `json:"group"`
Txqlen int `json:"txqlen"`
LinkType string `json:"link_type"`
Address string `json:"address"`
Broadcast string `json:"broadcast"`
AddrInfo []struct {
Family string `json:"family"`
Local string `json:"local"`
Prefixlen int `json:"prefixlen"`
Scope string `json:"scope"`
Label string `json:"label,omitempty"`
ValidLifeTime int64 `json:"valid_life_time"`
PreferredLifeTime int64 `json:"preferred_life_time"`
Broadcast string `json:"broadcast,omitempty"`
Dynamic bool `json:"dynamic,omitempty"`
Mngtmpaddr bool `json:"mngtmpaddr,omitempty"`
} `json:"addr_info"`
Link string `json:"link,omitempty"`
Master string `json:"master,omitempty"`
LinkIndex int `json:"link_index,omitempty"`
LinkNetnsid int `json:"link_netnsid,omitempty"`
}
func (iface *Iface) GetAddress() string {
var addrs []string
for _, addr := range iface.AddrInfo {
if addr.Family != "inet" {
continue
}
ip := net.ParseIP(addr.Local)
if ip == nil || ip.IsLoopback() {
continue
}
switch addr.Scope {
case "global":
addrs = append([]string{addr.Local}, addrs...)
case "link":
addrs = append(addrs, addr.Local)
}
}
if len(addrs) > 0 {
return addrs[0]
}
return ""
}
func (iface *Iface) GetAddress6() string {
var addrs []string
for _, addr := range iface.AddrInfo {
if addr.Family != "inet6" {
continue
}
ip := net.ParseIP(addr.Local)
if ip == nil || ip.IsLoopback() || ip.IsLinkLocalUnicast() {
continue
}
switch addr.Scope {
case "global":
addrs = append([]string{addr.Local}, addrs...)
case "link":
addrs = append(addrs, addr.Local)
}
}
if len(addrs) > 0 {
return addrs[0]
}
return ""
}
func GetIfaces(namespace string) (ifaces []*Iface, err error) {
output := ""
if namespace == "" {
output, err = utils.ExecOutput(
"",
"ip", "-j", "address",
)
} else {
output, err = utils.ExecOutput(
"",
"ip", "netns", "exec", namespace,
"ip", "-j", "address",
)
}
if err != nil {
return
}
ifaces = []*Iface{}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "ip: Failed to parse ip json output"),
}
return
}
return
}
func GetIfacesCached(namespace string) (ifacesMap map[string]*Iface, err error) {
cacheLock.Lock()
if time.Since(cacheTime[namespace]) < 5*time.Minute {
ifacesMap = cache[namespace]
cacheLock.Unlock()
return
}
cacheLock.Unlock()
ifaces, err := GetIfaces(namespace)
if err != nil {
return
}
ifacesMap = map[string]*Iface{}
for _, iface := range ifaces {
ifacesMap[iface.Ifname] = iface
}
cacheLock.Lock()
cache[namespace] = ifacesMap
cacheTime[namespace] = time.Now()
cacheLock.Unlock()
return
}
func ClearIfacesCache(namespace string) {
cacheLock.Lock()
cache[namespace] = map[string]*Iface{}
cacheTime[namespace] = time.Time{}
cacheLock.Unlock()
}
================================================
FILE: iproute/address.go
================================================
package iproute
import (
"encoding/json"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Address struct {
Family string `json:"family"`
Local string `json:"local"`
Prefix int `json:"prefixlen"`
Scope string `json:"scope"`
Label string `json:"label"`
Dynamic bool `json:"dynamic"`
Deprecated bool `json:"deprecated"`
}
type AddressIface struct {
Name string `json:"ifname"`
State string `json:"operstate"`
Addresses []*Address `json:"addr_info"`
}
func AddressGetIface(namespace, name string) (
address, address6 *Address, err error) {
ifaces := []*AddressIface{}
label := ""
if strings.Contains(name, ":") {
label = name
name = strings.Split(name, ":")[0]
}
var output string
if namespace != "" {
output, err = utils.ExecOutputLogged(
[]string{
"No such file or directory",
"does not exist",
"setting the network namespace",
},
"ip", "netns", "exec", namespace,
"ip", "--json",
"addr", "show",
"dev", name,
)
} else {
output, err = utils.ExecOutputLogged(
[]string{
"No such file or directory",
"does not exist",
"setting the network namespace",
},
"ip", "--json",
"addr", "show",
"dev", name,
)
}
if err != nil {
return
}
if output == "" {
return
}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "iproute: Failed to parse interface address"),
}
return
}
dynamic6 := false
if label != "" {
for _, iface := range ifaces {
if iface.Name == name && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Label == label && addr.Scope == "global" &&
!addr.Deprecated {
if address == nil && addr.Family == "inet" {
address = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
address6 = addr
dynamic6 = true
} else if address6 == nil {
address6 = addr
}
}
}
}
}
}
}
for _, iface := range ifaces {
if iface.Name == name && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Scope == "global" && !addr.Deprecated {
if address == nil && addr.Family == "inet" {
address = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
address6 = addr
dynamic6 = true
} else if address6 == nil {
address6 = addr
}
}
}
}
}
}
return
}
func AddressGetIfaceMod(namespace, name string) (
address, address6 *Address, err error) {
ifaces := []*AddressIface{}
label := ""
if strings.Contains(name, ":") {
label = name
name = strings.Split(name, ":")[0]
}
nameMod := name + "x"
nameMod6 := name + "y"
var addressMod *Address
var address6Mod *Address
var addressMod6 *Address
var address6Mod6 *Address
var output string
if namespace != "" {
output, err = utils.ExecOutputLogged(
[]string{
"No such file or directory",
"does not exist",
"setting the network namespace",
},
"ip", "netns", "exec", namespace,
"ip", "--json",
"addr", "show",
)
} else {
output, err = utils.ExecOutputLogged(
[]string{
"No such file or directory",
"does not exist",
"setting the network namespace",
},
"ip", "--json",
"addr", "show",
)
}
if err != nil {
return
}
if output == "" {
return
}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "iproute: Failed to parse interface address"),
}
return
}
dynamic6 := false
if label != "" {
for _, iface := range ifaces {
if strings.HasPrefix(iface.Name, name) && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Label == label && addr.Scope == "global" &&
!addr.Deprecated {
if address == nil && addr.Family == "inet" {
address = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
address6 = addr
dynamic6 = true
} else if address6 == nil {
address6 = addr
}
}
}
}
}
}
}
for _, iface := range ifaces {
if iface.Name == name && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Scope == "global" && !addr.Deprecated {
if address == nil && addr.Family == "inet" {
address = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
address6 = addr
dynamic6 = true
} else if address6 == nil {
address6 = addr
}
}
}
}
}
if iface.Name == nameMod && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Scope == "global" && !addr.Deprecated {
if addressMod == nil && addr.Family == "inet" {
addressMod = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
addressMod6 = addr
dynamic6 = true
} else if addressMod6 == nil {
addressMod6 = addr
}
}
}
}
}
if iface.Name == nameMod6 && iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Scope == "global" && !addr.Deprecated {
if address6Mod == nil && addr.Family == "inet" {
address6Mod = addr
} else if addr.Family == "inet6" {
if addr.Dynamic && !dynamic6 {
address6Mod6 = addr
dynamic6 = true
} else if address6Mod6 == nil {
address6Mod6 = addr
}
}
}
}
}
}
if address6Mod6 != nil {
address6 = address6Mod6
} else if addressMod6 != nil {
address6 = addressMod6
}
if addressMod != nil {
address = addressMod
} else if address6Mod != nil {
address = address6Mod
}
return
}
================================================
FILE: iproute/bridge.go
================================================
package iproute
import (
"github.com/pritunl/pritunl-cloud/utils"
)
func BridgeAdd(namespace, name string) (err error) {
if namespace != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "netns", "exec", namespace,
"ip", "link",
"add", name,
"type", "bridge",
"stp_state", "0",
"forward_delay", "0",
)
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "link",
"add", name,
"type", "bridge",
"stp_state", "0",
"forward_delay", "0",
)
}
if err != nil {
return
}
return
}
func BridgeDelete(namespace, name string) (err error) {
if namespace != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "netns", "exec", namespace,
"ip", "link",
"delete", name,
"type", "bridge",
)
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "link",
"delete", name,
"type", "bridge",
)
}
if err != nil {
return
}
return
}
================================================
FILE: iproute/iface.go
================================================
package iproute
import (
"encoding/json"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Iface struct {
Name string `json:"ifname"`
State string `json:"operstate"`
}
func IfaceGetAll(namespace string) (ifaces []*Iface, err error) {
ifaces = []*Iface{}
var output string
if namespace != "" {
output, err = utils.ExecOutputLogged(
nil,
"ip", "netns", "exec", namespace,
"ip", "--json", "--brief",
"link", "show",
)
} else {
output, err = utils.ExecOutputLogged(
nil,
"ip", "--json", "--brief",
"link", "show",
)
}
if err != nil {
return
}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "iproute: Failed to prase ifaces"),
}
return
}
return
}
func IfaceGetBridges(namespace string) (ifaces []*Iface, err error) {
ifaces = []*Iface{}
var output string
if namespace != "" {
output, err = utils.ExecOutputLogged(
nil,
"ip", "netns", "exec", namespace,
"ip", "--json", "--brief",
"link", "show",
"type", "bridge",
)
} else {
output, err = utils.ExecOutputLogged(
nil,
"ip", "--json", "--brief",
"link", "show",
"type", "bridge",
)
}
if err != nil {
return
}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "iproute: Failed to prase bridges"),
}
return
}
return
}
func IfaceGetBridgeIfaces(namespace, bridge string) (
ifaces []*Iface, err error) {
ifaces = []*Iface{}
var output string
if namespace != "" {
output, err = utils.ExecCombinedOutputLogged(
[]string{
"does not exist",
},
"ip", "netns", "exec", namespace,
"ip", "--json", "--brief",
"link", "show",
"master", bridge,
)
} else {
output, err = utils.ExecCombinedOutputLogged(
[]string{
"does not exist",
},
"ip", "--json", "--brief",
"link", "show",
"master", bridge,
)
}
if err != nil {
return
}
if output == "" {
return
}
err = json.Unmarshal([]byte(output), &ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "iproute: Failed to prase bridges"),
}
return
}
return
}
================================================
FILE: ipset/names.go
================================================
package ipset
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/utils"
)
type Names struct {
Namespace string
Sets set.Set
}
func (n *Names) Apply(curNames *Names) (err error) {
if curNames != nil {
for nameInf := range curNames.Sets.Iter() {
name := nameInf.(string)
if !n.Sets.Contains(name) {
if n.Namespace == "0" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"not exist"},
"ipset", "destroy",
name,
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{"not exist"},
"ip", "netns", "exec", n.Namespace,
"ipset", "destroy",
name,
)
if err != nil {
return
}
}
}
}
}
return
}
================================================
FILE: ipset/sets.go
================================================
package ipset
import (
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/utils"
)
type Sets struct {
Namespace string
Sets map[string]set.Set
}
func (s *Sets) Apply(curSets *Sets) (err error) {
namesSet := set.NewSet()
for name, ipSet := range s.Sets {
namesSet.Add(name)
var curIpSet set.Set
if curSets != nil {
curIpSet = curSets.Sets[name]
}
if curIpSet == nil {
curIpSet = set.NewSet()
}
created := false
for ipInf := range ipSet.Iter() {
ip := ipInf.(string)
if curIpSet.Contains(ip) {
continue
}
if !created {
family := "inet"
if strings.HasPrefix(name, "pr6") {
family = "inet6"
}
if s.Namespace == "0" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"already exists"},
"ipset", "create",
name, "hash:net",
"family", family,
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{"already exists"},
"ip", "netns", "exec", s.Namespace,
"ipset", "create",
name, "hash:net",
"family", family,
)
if err != nil {
return
}
}
created = true
}
if s.Namespace == "0" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"already added"},
"ipset", "add",
name, ip,
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{"already added"},
"ip", "netns", "exec", s.Namespace,
"ipset", "add",
name, ip,
)
if err != nil {
return
}
}
}
delIpSet := curIpSet.Copy()
delIpSet.Subtract(ipSet)
for ipInf := range delIpSet.Iter() {
ip := ipInf.(string)
if s.Namespace == "0" {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"not exist",
"not added",
},
"ipset", "del",
name, ip,
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"not exist",
"not added",
},
"ip", "netns", "exec", s.Namespace,
"ipset", "del",
name, ip,
)
if err != nil {
return
}
}
}
}
return
}
================================================
FILE: ipset/state.go
================================================
package ipset
import (
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/firewall"
)
type State struct {
Namespaces map[string]*Sets
}
func (s *State) AddIngress(namespace string, ingress []*firewall.Rule) {
sets := s.Namespaces[namespace]
if sets == nil {
sets = &Sets{
Namespace: namespace,
Sets: map[string]set.Set{},
}
s.Namespaces[namespace] = sets
}
for _, rule := range ingress {
name := rule.SetName(false)
name6 := rule.SetName(true)
if name == "" || name6 == "" || rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
continue
}
for _, sourceIp := range rule.SourceIps {
if sourceIp == "0.0.0.0/0" || sourceIp == "::/0" {
continue
}
ruleName := ""
ipv6 := strings.Contains(sourceIp, ":")
if ipv6 {
sourceIp = strings.Replace(sourceIp, "/128", "", 1)
ruleName = name6
} else {
sourceIp = strings.Replace(sourceIp, "/32", "", 1)
ruleName = name
}
ruleSet := sets.Sets[ruleName]
if ruleSet == nil {
ruleSet = set.NewSet()
sets.Sets[ruleName] = ruleSet
}
ruleSet.Add(sourceIp)
}
}
}
func (s *State) AddSourceDestCheck(namespace, addr6 string) {
sets := s.Namespaces[namespace]
if sets == nil {
sets = &Sets{
Namespace: namespace,
Sets: map[string]set.Set{},
}
s.Namespaces[namespace] = sets
}
sdcSet := set.NewSet()
if addr6 != "" {
sdcSet.Add(addr6)
}
sdcSet.Add("fe80::/10")
sets.Sets["pr6_sdc"] = sdcSet
}
func (s *State) AddMember(namespace string, ruleName, member string) {
if strings.HasPrefix(ruleName, "prx") {
return
}
sets := s.Namespaces[namespace]
if sets == nil {
sets = &Sets{
Namespace: namespace,
Sets: map[string]set.Set{},
}
s.Namespaces[namespace] = sets
}
ruleSet := sets.Sets[ruleName]
if ruleSet == nil {
ruleSet = set.NewSet()
sets.Sets[ruleName] = ruleSet
}
ruleSet.Add(member)
}
type NamesState struct {
Namespaces map[string]*Names
}
func (n *NamesState) AddIngress(namespace string, ingress []*firewall.Rule) {
sets := n.Namespaces[namespace]
if sets == nil {
sets = &Names{
Namespace: namespace,
Sets: set.NewSet(),
}
n.Namespaces[namespace] = sets
}
for _, rule := range ingress {
name := rule.SetName(false)
name6 := rule.SetName(true)
if name == "" || name6 == "" || rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
continue
}
for _, sourceIp := range rule.SourceIps {
if sourceIp == "0.0.0.0/0" || sourceIp == "::/0" {
continue
}
ipv6 := strings.Contains(sourceIp, ":")
if ipv6 {
sets.Sets.Add(name6)
} else {
sets.Sets.Add(name)
}
}
}
}
func (n *NamesState) AddSourceDestCheck(namespace string) {
sets := n.Namespaces[namespace]
if sets == nil {
sets = &Names{
Namespace: namespace,
Sets: set.NewSet(),
}
n.Namespaces[namespace] = sets
}
sets.Sets.Add("pr6_sdc")
}
func (n *NamesState) AddName(namespace string, ruleName string) {
if strings.HasPrefix(ruleName, "prx") {
return
}
sets := n.Namespaces[namespace]
if sets == nil {
sets = &Names{
Namespace: namespace,
Sets: set.NewSet(),
}
n.Namespaces[namespace] = sets
}
sets.Sets.Add(ruleName)
}
================================================
FILE: ipset/utils.go
================================================
package ipset
import (
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
var (
curState *State
curNamesState *NamesState
stateLock = utils.NewTimeoutLock(3 * time.Minute)
)
func UpdateState(instances []*instance.Instance, namespaces []string,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (
err error) {
lockId := stateLock.Lock()
defer stateLock.Unlock(lockId)
newState := &State{
Namespaces: map[string]*Sets{},
}
if nodeFirewall != nil {
newState.AddIngress("0", nodeFirewall)
}
for _, inst := range instances {
if !inst.IsActive() {
continue
}
for i := range inst.Virt.NetworkAdapters {
namespace := vm.GetNamespace(inst.Id, i)
ingress := firewalls[namespace]
if ingress == nil {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"namespace": namespace,
}).Warn("ipset: Failed to load instance firewall rules")
continue
}
addr6 := ""
if inst.PrivateIps6 != nil && len(inst.PrivateIps6) != 0 {
addr6 = inst.PrivateIps6[0]
}
newState.AddIngress(namespace, ingress)
if !inst.SkipSourceDestCheck {
newState.AddSourceDestCheck(namespace, addr6)
}
}
}
err = applyState(curState, newState, namespaces)
if err != nil {
return
}
curState = newState
return
}
func applyState(oldState, newState *State, namespaces []string) (err error) {
namespacesSet := set.NewSet()
for _, namespace := range namespaces {
namespacesSet.Add(namespace)
}
for _, ipSet := range newState.Namespaces {
if ipSet.Namespace != "0" && !namespacesSet.Contains(
ipSet.Namespace) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns",
"add", ipSet.Namespace,
)
if err != nil {
return
}
}
curSet := oldState.Namespaces[ipSet.Namespace]
err = ipSet.Apply(curSet)
if err != nil {
return
}
}
return
}
func UpdateNamesState(instances []*instance.Instance,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (
err error) {
lockId := stateLock.Lock()
defer stateLock.Unlock(lockId)
newNamesState := &NamesState{
Namespaces: map[string]*Names{},
}
if nodeFirewall != nil {
newNamesState.AddIngress("0", nodeFirewall)
}
for _, inst := range instances {
if !inst.IsActive() {
continue
}
for i := range inst.Virt.NetworkAdapters {
namespace := vm.GetNamespace(inst.Id, i)
ingress := firewalls[namespace]
if ingress == nil {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"namespace": namespace,
}).Warn("ipset: Failed to load instance firewall rules")
continue
}
newNamesState.AddIngress(namespace, ingress)
if !inst.SkipSourceDestCheck {
newNamesState.AddSourceDestCheck(namespace)
}
}
}
err = applyNamesState(curNamesState, newNamesState)
if err != nil {
return
}
curNamesState = newNamesState
return
}
func applyNamesState(oldNamesState, newNamesState *NamesState) (err error) {
for _, ipSet := range newNamesState.Namespaces {
curSet := oldNamesState.Namespaces[ipSet.Namespace]
err = ipSet.Apply(curSet)
if err != nil {
return
}
}
return
}
func loadIpset(namespace string, state *State, namesState *NamesState) (
err error) {
output := ""
if namespace == "0" {
output, err = utils.ExecOutput("", "ipset", "list")
if err != nil {
return
}
} else {
output, err = utils.ExecOutput("",
"ip", "netns", "exec", namespace, "ipset", "list")
if err != nil {
return
}
}
curName := ""
isMembers := false
for _, line := range strings.Split(output, "\n") {
if strings.HasPrefix(line, "Name:") {
curName = strings.TrimSpace(strings.SplitN(line, ":", 2)[1])
isMembers = false
} else if isMembers {
if line == "" {
isMembers = false
} else {
member := strings.TrimSpace(line)
state.AddMember(namespace, curName, member)
namesState.AddName(namespace, curName)
}
} else if strings.HasPrefix(line, "Members:") {
isMembers = true
}
}
return
}
func Init(namespaces []string, instances []*instance.Instance,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (
err error) {
state := &State{
Namespaces: map[string]*Sets{},
}
namesState := &NamesState{
Namespaces: map[string]*Names{},
}
err = loadIpset("0", state, namesState)
if err != nil {
return
}
for _, namespace := range namespaces {
err = loadIpset(namespace, state, namesState)
if err != nil {
return
}
}
curState = state
curNamesState = namesState
err = UpdateState(instances, namespaces, nodeFirewall, firewalls)
if err != nil {
return
}
return
}
func InitNames(namespaces []string, instances []*instance.Instance,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule) (
err error) {
err = UpdateNamesState(instances, nodeFirewall, firewalls)
if err != nil {
return
}
return
}
================================================
FILE: iptables/iptables.go
================================================
package iptables
import (
"fmt"
"sort"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/ipvs"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
var (
curState *State
stateLock = utils.NewTimeoutLock(3 * time.Minute)
)
func (r *Rules) newCommand() (cmd []string) {
chain := ""
if r.Interface == "host" {
chain = "INPUT"
} else {
chain = "FORWARD"
}
cmd = []string{
chain,
}
return
}
func (r *Rules) newCommandNatPre() (cmd []string) {
cmd = []string{
"PREROUTING",
}
return
}
func (r *Rules) newCommandNatPost() (cmd []string) {
cmd = []string{
"POSTROUTING",
}
return
}
func (r *Rules) newCommandMap() (cmd []string) {
cmd = []string{
"PREROUTING",
}
return
}
func (r *Rules) newCommandMapPost() (cmd []string) {
cmd = []string{
"POSTROUTING",
}
return
}
func (r *Rules) commentCommand(inCmd []string, hold bool) (cmd []string) {
comment := ""
if hold {
comment = "pritunl_cloud_hold"
} else {
comment = "pritunl_cloud_rule"
}
cmd = append(inCmd,
"-m", "comment",
"--comment", comment,
)
return
}
func (r *Rules) commentCommandHeader(inCmd []string) (cmd []string) {
cmd = append(inCmd,
"-m", "comment",
"--comment", "pritunl_cloud_head",
)
return
}
func (r *Rules) commentCommandSdc(inCmd []string) (cmd []string) {
cmd = append(inCmd,
"-m", "comment",
"--comment", "pritunl_cloud_sdc",
)
return
}
func (r *Rules) commentCommandNat(inCmd []string) (cmd []string) {
cmd = append(inCmd,
"-m", "comment",
"--comment", "pritunl_cloud_nat",
)
return
}
func (r *Rules) commentCommandMap(inCmd []string) (cmd []string) {
cmd = append(inCmd,
"-m", "comment",
"--comment", "pritunl_cloud_map",
)
return
}
func (r *Rules) run(table string, cmds [][]string,
ipCmd string, ipv6 bool) (err error) {
iptablesCmd := getIptablesCmd(ipv6)
for _, cmd := range cmds {
cmd = append([]string{ipCmd}, cmd...)
if table != "" {
cmd = append([]string{"-t", table}, cmd...)
}
if r.Namespace != "0" {
cmd = append([]string{
"netns", "exec", r.Namespace,
iptablesCmd,
}, cmd...)
}
for i := 0; i < 3; i++ {
output := ""
if r.Namespace == "0" {
Lock()
output, err = utils.ExecCombinedOutputLogged(
[]string{
"matching rule exist",
}, iptablesCmd, cmd...)
Unlock()
} else {
Lock()
output, err = utils.ExecCombinedOutputLogged(
[]string{
"matching rule exist",
"Cannot open network namespace",
}, "ip", cmd...)
Unlock()
}
if err != nil {
if i < 2 {
err = nil
time.Sleep(250 * time.Millisecond)
continue
} else if cmd[len(cmd)-1] == "ACCEPT" {
err = nil
logrus.WithFields(logrus.Fields{
"ipv6": ipv6,
"command": cmd,
"output": output,
}).Error("iptables: Ignoring invalid iptables command")
} else {
logrus.WithFields(logrus.Fields{
"ipv6": ipv6,
"command": cmd,
"output": output,
}).Warn("iptables: Failed to run iptables command")
return
}
}
break
}
}
return
}
func (r *Rules) Apply(diff *RulesDiff) (err error) {
if diff == nil || diff.HeaderDiff {
err = r.run("", r.Header, "-A", false)
if err != nil {
return
}
}
if diff == nil || diff.Header6Diff {
err = r.run("", r.Header6, "-A", true)
if err != nil {
return
}
}
if diff == nil || diff.SourceDestCheckDiff {
err = r.run("", r.SourceDestCheck, "-A", false)
if err != nil {
return
}
}
if diff == nil || diff.SourceDestCheck6Diff {
err = r.run("", r.SourceDestCheck6, "-A", true)
if err != nil {
return
}
}
if diff == nil || diff.IngressDiff {
err = r.run("", r.Ingress, "-A", false)
if err != nil {
return
}
}
if diff == nil || diff.Ingress6Diff {
err = r.run("", r.Ingress6, "-A", true)
if err != nil {
return
}
}
if diff == nil || diff.NatsDiff {
err = r.run("nat", r.Nats, "-A", false)
if err != nil {
return
}
}
if diff == nil || diff.Nats6Diff {
err = r.run("nat", r.Nats6, "-A", true)
if err != nil {
return
}
}
if diff == nil || diff.MapsDiff {
err = r.run("nat", r.Maps, "-A", false)
if err != nil {
return
}
}
if diff == nil || diff.Maps6Diff {
err = r.run("nat", r.Maps6, "-A", true)
if err != nil {
return
}
}
if diff == nil || diff.IngressDiff {
err = r.run("", r.Holds, "-D", false)
if err != nil {
return
}
r.Holds = [][]string{}
}
if diff == nil || diff.Ingress6Diff {
err = r.run("", r.Holds6, "-D", true)
if err != nil {
return
}
r.Holds6 = [][]string{}
}
return
}
func (r *Rules) Hold() (err error) {
cmd := r.newCommand()
if r.Interface != "host" {
if strings.HasPrefix(r.Interface, "e") {
cmd = append(cmd,
"-i", r.Interface+"+",
)
} else if strings.HasPrefix(r.Interface, "h") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "m") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "i") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "o") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "p") {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", r.Interface,
"--physdev-is-bridged",
)
} else {
err = &errortypes.ParseError{
errors.Newf("iptables: Unknown interface type %s",
r.Interface),
}
return
}
}
cmd = r.commentCommand(cmd, true)
cmd = append(cmd,
"-j", "DROP",
)
r.Holds = append(r.Holds, cmd)
cmd = r.newCommand()
if r.Interface != "host" {
if strings.HasPrefix(r.Interface, "e") {
cmd = append(cmd,
"-i", r.Interface+"+",
)
} else if strings.HasPrefix(r.Interface, "h") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "m") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "i") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "o") {
cmd = append(cmd,
"-i", r.Interface,
)
} else if strings.HasPrefix(r.Interface, "p") {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", r.Interface,
"--physdev-is-bridged",
)
} else {
err = &errortypes.ParseError{
errors.Newf("iptables: Unknown interface type %s",
r.Interface),
}
return
}
}
cmd = r.commentCommand(cmd, true)
cmd = append(cmd,
"-j", "DROP",
)
r.Holds6 = append(r.Holds6, cmd)
err = r.run("", r.Holds, "-A", false)
if err != nil {
return
}
err = r.run("", r.Holds6, "-A", true)
if err != nil {
return
}
return
}
func (r *Rules) Remove(diff *RulesDiff) (err error) {
if diff == nil || diff.HeaderDiff {
err = r.run("", r.Header, "-D", false)
if err != nil {
return
}
r.Header = [][]string{}
}
if diff == nil || diff.Header6Diff {
err = r.run("", r.Header6, "-D", true)
if err != nil {
return
}
r.Header6 = [][]string{}
}
if diff == nil || diff.SourceDestCheckDiff {
err = r.run("", r.SourceDestCheck, "-D", false)
if err != nil {
return
}
r.SourceDestCheck = [][]string{}
}
if diff == nil || diff.SourceDestCheck6Diff {
err = r.run("", r.SourceDestCheck6, "-D", true)
if err != nil {
return
}
r.SourceDestCheck6 = [][]string{}
}
if diff == nil || diff.IngressDiff {
err = r.run("", r.Ingress, "-D", false)
if err != nil {
return
}
r.Ingress = [][]string{}
}
if diff == nil || diff.Ingress6Diff {
err = r.run("", r.Ingress6, "-D", true)
if err != nil {
return
}
r.Ingress6 = [][]string{}
}
if diff == nil || diff.NatsDiff {
err = r.run("nat", r.Nats, "-D", false)
if err != nil {
return
}
r.Nats = [][]string{}
}
if diff == nil || diff.Nats6Diff {
err = r.run("nat", r.Nats6, "-D", true)
if err != nil {
return
}
r.Nats6 = [][]string{}
}
if diff == nil || diff.MapsDiff {
err = r.run("nat", r.Maps, "-D", false)
if err != nil {
return
}
r.Maps = [][]string{}
}
if diff == nil || diff.Maps6Diff {
err = r.run("nat", r.Maps6, "-D", true)
if err != nil {
return
}
r.Maps6 = [][]string{}
}
err = r.run("", r.Holds, "-D", false)
if err != nil {
return
}
r.Holds = [][]string{}
err = r.run("", r.Holds6, "-D", true)
if err != nil {
return
}
r.Holds6 = [][]string{}
return
}
func generateVirt(vc *vpc.Vpc, namespace, iface, addr, addr6 string,
sourceDestCheck bool, ingress []*firewall.Rule) (rules *Rules) {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
cmd := rules.newCommand()
cmd = append(cmd,
"-p", "ipv6-icmp",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "icmp6",
"--icmpv6-type", "133",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
cmd = append(cmd,
"-p", "ipv6-icmp",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "icmp6",
"--icmpv6-type", "134",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
cmd = append(cmd,
"-p", "ipv6-icmp",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "icmp6",
"--icmpv6-type", "135",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
cmd = append(cmd,
"-p", "ipv6-icmp",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "icmp6",
"--icmpv6-type", "136",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
if sourceDestCheck {
if addr != "" {
cmd := rules.newCommand()
cmd = append(cmd,
"!", "-s", addr+"/32",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-in", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommandSdc(cmd)
cmd = append(cmd,
"-j", "DROP",
)
rules.SourceDestCheck = append(rules.SourceDestCheck, cmd)
}
cmd := rules.newCommand()
cmd = append(cmd,
"-m", "set",
"!", "--match-set", "pr6_sdc", "src",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-in", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommandSdc(cmd)
cmd = append(cmd,
"-j", "DROP",
)
rules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)
if addr != "" {
cmd = rules.newCommand()
cmd = append(cmd,
"!", "-d", addr+"/32",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommandSdc(cmd)
cmd = append(cmd,
"-j", "DROP",
)
rules.SourceDestCheck = append(rules.SourceDestCheck, cmd)
}
cmd = rules.newCommand()
cmd = append(cmd,
"-m", "set",
"!", "--match-set", "pr6_sdc", "dst",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommandSdc(cmd)
cmd = append(cmd,
"-j", "DROP",
)
rules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)
}
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
for _, rule := range ingress {
all4 := false
all6 := false
set4 := false
set6 := false
setName := rule.SetName(false)
setName6 := rule.SetName(true)
if setName == "" || setName6 == "" {
continue
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.newCommand()
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
continue
}
for _, sourceIp := range rule.SourceIps {
ipv6 := strings.Contains(sourceIp, ":")
if sourceIp == "0.0.0.0/0" {
if all4 {
continue
}
all4 = true
} else if sourceIp == "::/0" {
if all6 {
continue
}
all6 = true
} else {
if ipv6 {
if set6 {
continue
}
set6 = true
} else {
if set4 {
continue
}
set4 = true
}
}
cmd = rules.newCommand()
switch rule.Protocol {
case firewall.All:
break
case firewall.Icmp:
if ipv6 {
cmd = append(cmd,
"-p", "ipv6-icmp",
)
} else {
cmd = append(cmd,
"-p", "icmp",
)
}
break
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-p", rule.Protocol,
)
break
default:
continue
}
if sourceIp != "0.0.0.0/0" && sourceIp != "::/0" &&
rule.Protocol != firewall.Multicast &&
rule.Protocol != firewall.Broadcast {
if ipv6 {
cmd = append(cmd,
"-m", "set",
"--match-set", setName6, "src",
)
} else {
cmd = append(cmd,
"-m", "set",
"--match-set", setName, "src",
)
}
}
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
switch rule.Protocol {
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-m", rule.Protocol,
"--dport", strings.Replace(rule.Port, "-", ":", 1),
"-m", "conntrack",
"--ctstate", "NEW",
)
break
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Header6 = append(rules.Header6, cmd)
} else {
rules.Header = append(rules.Header, cmd)
}
} else {
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Ingress6 = append(rules.Ingress6, cmd)
} else {
rules.Ingress = append(rules.Ingress, cmd)
}
}
}
}
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-m", "physdev",
"--physdev-out", rules.Interface,
"--physdev-is-bridged",
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
if vc != nil && vc.Maps != nil {
for _, mp := range vc.Maps {
if mp.Type != vpc.Destination {
continue
}
if strings.Contains(mp.Target, ":") {
if addr6 != "" {
cmd = rules.newCommandMap()
cmd = append(cmd,
"-s", addr+"/128",
"-d", mp.Destination,
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", mp.Target,
)
rules.Maps6 = append(rules.Maps6, cmd)
}
} else {
if addr != "" {
cmd = rules.newCommandMap()
cmd = append(cmd,
"-s", addr+"/32",
"-d", mp.Destination,
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", mp.Target,
)
rules.Maps = append(rules.Maps, cmd)
}
}
}
}
return
}
func generateInternal(namespace, iface string, nat, nat6, dhcp, dhcp6 bool,
natAddr, natPubAddr, natAddr6, natPubAddr6 string,
ingress []*firewall.Rule) (rules *Rules) {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
if strings.HasPrefix(iface, "e") {
iface = iface + "+"
}
if nat && natAddr != "" && natPubAddr != "" {
cmd := rules.newCommandNatPre()
cmd = append(cmd,
"-d", natPubAddr+"/32",
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", natAddr,
)
rules.Nats = append(rules.Nats, cmd)
cmd = rules.newCommandNatPost()
cmd = append(cmd,
"-s", natAddr+"/32",
"-d", natAddr+"/32",
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "SNAT",
"--to-source", natPubAddr,
)
rules.Nats = append(rules.Nats, cmd)
cmd = rules.newCommandNatPost()
cmd = append(cmd,
"-s", natAddr+"/32",
"-o", iface,
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "MASQUERADE",
)
rules.Nats = append(rules.Nats, cmd)
}
if nat6 && natAddr6 != "" && natPubAddr6 != "" {
cmd := rules.newCommandNatPre()
cmd = append(cmd,
"-d", natPubAddr6+"/128",
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", natAddr6,
)
rules.Nats6 = append(rules.Nats6, cmd)
cmd = rules.newCommandNatPost()
cmd = append(cmd,
"-s", natAddr6+"/128",
"-d", natAddr6+"/128",
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "SNAT",
"--to-source", natPubAddr6,
)
rules.Nats6 = append(rules.Nats6, cmd)
cmd = rules.newCommandNatPost()
cmd = append(cmd,
"-s", natAddr6+"/128",
"-o", iface,
)
cmd = rules.commentCommandNat(cmd)
cmd = append(cmd,
"-j", "MASQUERADE",
)
rules.Nats6 = append(rules.Nats6, cmd)
}
cmd := rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "133",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "134",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "135",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "136",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
for _, rule := range ingress {
all4 := false
all6 := false
set4 := false
set6 := false
setName := rule.SetName(false)
setName6 := rule.SetName(true)
if setName == "" || setName6 == "" {
continue
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
continue
}
for _, sourceIp := range rule.SourceIps {
ipv6 := strings.Contains(sourceIp, ":")
if sourceIp == "0.0.0.0/0" {
if all4 {
continue
}
all4 = true
} else if sourceIp == "::/0" {
if all6 {
continue
}
all6 = true
} else {
if ipv6 {
if set6 {
continue
}
set6 = true
} else {
if set4 {
continue
}
set4 = true
}
}
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
switch rule.Protocol {
case firewall.All:
break
case firewall.Icmp:
if ipv6 {
cmd = append(cmd,
"-p", "ipv6-icmp",
)
} else {
cmd = append(cmd,
"-p", "icmp",
)
}
break
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-p", rule.Protocol,
)
break
default:
continue
}
if sourceIp != "0.0.0.0/0" && sourceIp != "::/0" &&
rule.Protocol != firewall.Multicast &&
rule.Protocol != firewall.Broadcast {
if ipv6 {
cmd = append(cmd,
"-m", "set",
"--match-set", setName6, "src",
)
} else {
cmd = append(cmd,
"-m", "set",
"--match-set", setName, "src",
)
}
}
switch rule.Protocol {
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-m", rule.Protocol,
"--dport", strings.Replace(rule.Port, "-", ":", 1),
"-m", "conntrack",
"--ctstate", "NEW",
)
break
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Header6 = append(rules.Header6, cmd)
} else {
rules.Header = append(rules.Header, cmd)
}
} else {
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Ingress6 = append(rules.Ingress6, cmd)
} else {
rules.Ingress = append(rules.Ingress, cmd)
}
}
}
}
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if iface != "host" {
cmd = append(cmd,
"-i", iface,
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
return
}
func generateNodePort(namespace, iface string, addr, nodePortGateway string,
firewallMaps []*firewall.Mapping) (rules *Rules) {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
cmd := rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-i", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
for _, mapping := range firewallMaps {
if mapping.Protocol != firewall.Tcp &&
mapping.Protocol != firewall.Udp {
continue
}
cmd = rules.newCommandMap()
cmd = append(cmd,
"-i", iface,
"-p", mapping.Protocol,
"-m", mapping.Protocol,
"--dport", fmt.Sprintf("%d", mapping.ExternalPort),
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", fmt.Sprintf(
"%s:%d",
addr,
mapping.InternalPort,
),
)
rules.Maps = append(rules.Maps, cmd)
cmd = rules.newCommand()
cmd = append(cmd,
"-s", nodePortGateway+"/32",
)
if rules.Interface != "host" {
cmd = append(cmd,
"-i", rules.Interface,
)
}
cmd = append(cmd,
"-p", mapping.Protocol,
"-m", mapping.Protocol,
"--dport", fmt.Sprintf("%d", mapping.InternalPort),
"-m", "conntrack",
"--ctstate", "NEW",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
}
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-i", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-i", rules.Interface,
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
return
}
func generateHost(namespace, iface string, nodePortNetwork bool,
nodePortGateway, defaultIface string, externalIfaces, puiblicIps []string,
ingress []*firewall.Rule,
nodePortMappings map[string][]*firewall.Mapping) (rules *Rules) {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
Ipvs: ipvs.New(),
}
if rules.Interface == "host" {
cmd := rules.newCommand()
cmd = append(cmd,
"-i", "lo",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
}
if rules.Interface == "host" {
cmd := rules.newCommand()
cmd = append(cmd,
"-i", "lo",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
}
cmd := rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "133",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "134",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "135",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-p", "ipv6-icmp",
"-m", "icmp6",
"--icmpv6-type", "136",
)
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Header6 = append(rules.Header6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
for _, rule := range ingress {
all4 := false
all6 := false
set4 := false
set6 := false
setName := rule.SetName(false)
setName6 := rule.SetName(true)
if setName == "" || setName6 == "" {
continue
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
rules.Ingress = append(rules.Ingress, cmd)
continue
}
for _, sourceIp := range rule.SourceIps {
ipv6 := strings.Contains(sourceIp, ":")
if sourceIp == "0.0.0.0/0" {
if all4 {
continue
}
all4 = true
} else if sourceIp == "::/0" {
if all6 {
continue
}
all6 = true
} else {
if ipv6 {
if set6 {
continue
}
set6 = true
} else {
if set4 {
continue
}
set4 = true
}
}
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
switch rule.Protocol {
case firewall.All:
break
case firewall.Icmp:
if ipv6 {
cmd = append(cmd,
"-p", "ipv6-icmp",
)
} else {
cmd = append(cmd,
"-p", "icmp",
)
}
break
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-p", "udp",
"-m", "pkttype",
"--pkt-type", rule.Protocol,
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-p", rule.Protocol,
)
break
default:
continue
}
if sourceIp != "0.0.0.0/0" && sourceIp != "::/0" &&
rule.Protocol != firewall.Multicast &&
rule.Protocol != firewall.Broadcast {
if ipv6 {
cmd = append(cmd,
"-m", "set",
"--match-set", setName6, "src",
)
} else {
cmd = append(cmd,
"-m", "set",
"--match-set", setName, "src",
)
}
}
switch rule.Protocol {
case firewall.Multicast, firewall.Broadcast:
cmd = append(cmd,
"-m", "udp",
"--dport", strings.Replace(rule.Port, "-", ":", 1),
)
break
case firewall.Tcp, firewall.Udp:
cmd = append(cmd,
"-m", rule.Protocol,
"--dport", strings.Replace(rule.Port, "-", ":", 1),
"-m", "conntrack",
"--ctstate", "NEW",
)
break
}
if rule.Protocol == firewall.Multicast ||
rule.Protocol == firewall.Broadcast {
cmd = rules.commentCommandHeader(cmd)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Header6 = append(rules.Header6, cmd)
} else {
rules.Header = append(rules.Header, cmd)
}
} else {
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "ACCEPT",
)
if ipv6 {
rules.Ingress6 = append(rules.Ingress6, cmd)
} else {
rules.Ingress = append(rules.Ingress, cmd)
}
}
}
}
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = append(cmd,
"-m", "conntrack",
"--ctstate", "INVALID",
)
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress = append(rules.Ingress, cmd)
cmd = rules.newCommand()
if rules.Interface != "host" {
cmd = append(cmd,
"-o", rules.Interface,
)
}
cmd = rules.commentCommand(cmd, false)
cmd = append(cmd,
"-j", "DROP",
)
rules.Ingress6 = append(rules.Ingress6, cmd)
if nodePortNetwork {
nodePortKeys := make([]string, 0, len(nodePortMappings))
for k := range nodePortMappings {
nodePortKeys = append(nodePortKeys, k)
}
sort.Strings(nodePortKeys)
for _, nodePortAddr := range nodePortKeys {
mappings := nodePortMappings[nodePortAddr]
for _, mapping := range mappings {
if mapping.Protocol != firewall.Tcp &&
mapping.Protocol != firewall.Udp {
continue
}
if mapping.Ipvs {
ipvsProtocol := ""
if mapping.Protocol == firewall.Udp {
ipvsProtocol = ipvs.Udp
} else {
ipvsProtocol = ipvs.Tcp
}
for _, publicIp := range puiblicIps {
rules.Ipvs.AddTarget(publicIp, mapping.Address,
mapping.ExternalPort, ipvsProtocol)
}
// Alternative to full SNAT
// cmd = rules.newCommandMapPost()
// cmd = append(cmd,
// "-m", "ipvs",
// "--vproto", protocolIndex(mapping.Protocol),
// "--vport", fmt.Sprintf("%d", mapping.ExternalPort),
// "--vdir", "ORIGINAL",
// )
// cmd = rules.commentCommandMap(cmd)
// cmd = append(cmd,
// "-j", "SNAT",
// "--to-source", nodePortGateway,
// )
// rules.Maps = append(rules.Maps, cmd)
} else {
nodePortIfaces := []string{}
if defaultIface != "" {
nodePortIfaces = append(nodePortIfaces, defaultIface)
}
for _, iface := range externalIfaces {
if iface != defaultIface {
nodePortIfaces = append(nodePortIfaces, iface)
}
}
for _, externalIface := range nodePortIfaces {
cmd = rules.newCommandMap()
cmd = append(cmd,
"-i", externalIface,
"-p", mapping.Protocol,
"-m", mapping.Protocol,
"--dport", fmt.Sprintf("%d", mapping.ExternalPort),
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", fmt.Sprintf(
"%s:%d",
nodePortAddr,
mapping.ExternalPort,
),
)
rules.Maps = append(rules.Maps, cmd)
}
}
}
}
cmd = rules.newCommandMapPost()
cmd = append(cmd,
"-o", settings.Hypervisor.NodePortNetworkName,
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "SNAT",
"--to-source", nodePortGateway,
)
rules.Maps = append(rules.Maps, cmd)
}
return
}
func generateHostNodePort(namespace, iface string,
nodePortGateway, defaultIface string, externalIfaces, puiblicIps []string,
nodePortMappings map[string][]*firewall.Mapping) (rules *Rules) {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
Ipvs: ipvs.New(),
}
nodePortKeys := make([]string, 0, len(nodePortMappings))
for k := range nodePortMappings {
nodePortKeys = append(nodePortKeys, k)
}
sort.Strings(nodePortKeys)
for _, nodePortAddr := range nodePortKeys {
mappings := nodePortMappings[nodePortAddr]
for _, mapping := range mappings {
if mapping.Protocol != firewall.Tcp &&
mapping.Protocol != firewall.Udp {
continue
}
if mapping.Ipvs {
ipvsProtocol := ""
if mapping.Protocol == firewall.Udp {
ipvsProtocol = ipvs.Udp
} else {
ipvsProtocol = ipvs.Tcp
}
for _, publicIp := range puiblicIps {
rules.Ipvs.AddTarget(publicIp, mapping.Address,
mapping.ExternalPort, ipvsProtocol)
}
// Alternative to full SNAT
// cmd = rules.newCommandMapPost()
// cmd = append(cmd,
// "-m", "ipvs",
// "--vproto", protocolIndex(mapping.Protocol),
// "--vport", fmt.Sprintf("%d", mapping.ExternalPort),
// "--vdir", "ORIGINAL",
// )
// cmd = rules.commentCommandMap(cmd)
// cmd = append(cmd,
// "-j", "SNAT",
// "--to-source", nodePortGateway,
// )
// rules.Maps = append(rules.Maps, cmd)
} else {
nodePortIfaces := []string{}
if defaultIface != "" {
nodePortIfaces = append(nodePortIfaces, defaultIface)
}
for _, iface := range externalIfaces {
if iface != defaultIface {
nodePortIfaces = append(nodePortIfaces, iface)
}
}
for _, externalIface := range nodePortIfaces {
cmd := rules.newCommandMap()
cmd = append(cmd,
"-i", externalIface,
"-p", mapping.Protocol,
"-m", mapping.Protocol,
"--dport", fmt.Sprintf("%d", mapping.ExternalPort),
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "DNAT",
"--to-destination", fmt.Sprintf(
"%s:%d",
nodePortAddr,
mapping.ExternalPort,
),
)
rules.Maps = append(rules.Maps, cmd)
}
}
}
}
cmd := rules.newCommandMapPost()
cmd = append(cmd,
"-o", settings.Hypervisor.NodePortNetworkName,
)
cmd = rules.commentCommandMap(cmd)
cmd = append(cmd,
"-j", "SNAT",
"--to-source", nodePortGateway,
)
rules.Maps = append(rules.Maps, cmd)
return
}
================================================
FILE: iptables/lock.go
================================================
package iptables
import (
"sync"
)
var lock = sync.Mutex{}
func Lock() {
lock.Lock()
}
func Unlock() {
lock.Unlock()
}
================================================
FILE: iptables/rules.go
================================================
package iptables
import (
"github.com/pritunl/pritunl-cloud/ipvs"
)
type Rules struct {
Namespace string
Interface string
Header [][]string
Header6 [][]string
SourceDestCheck [][]string
SourceDestCheck6 [][]string
Ingress [][]string
Ingress6 [][]string
Nats [][]string
Nats6 [][]string
Maps [][]string
Maps6 [][]string
Holds [][]string
Holds6 [][]string
Ipvs *ipvs.State
}
type RulesDiff struct {
HeaderDiff bool
Header6Diff bool
SourceDestCheckDiff bool
SourceDestCheck6Diff bool
IngressDiff bool
Ingress6Diff bool
NatsDiff bool
Nats6Diff bool
MapsDiff bool
Maps6Diff bool
HoldsDiff bool
Holds6Diff bool
}
================================================
FILE: iptables/state.go
================================================
package iptables
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
type State struct {
Interfaces map[string]*Rules
}
func LoadState(nodeSelf *node.Node, vpcs []*vpc.Vpc,
instances []*instance.Instance, nodeFirewall []*firewall.Rule,
firewalls map[string][]*firewall.Rule,
firewallMaps map[string][]*firewall.Mapping) (state *State) {
vpcsMap := map[bson.ObjectID]*vpc.Vpc{}
for _, vc := range vpcs {
vpcsMap[vc.Id] = vc
}
nodeNetworkMode := node.Self.NetworkMode
if nodeNetworkMode == "" {
nodeNetworkMode = node.Dhcp
}
nodeNetworkMode6 := node.Self.NetworkMode6
if nodeNetworkMode6 == "" {
nodeNetworkMode6 = node.Dhcp
}
nodePortNetwork := !node.Self.NoNodePortNetwork
state = &State{
Interfaces: map[string]*Rules{},
}
hostNodePortMappings := map[string][]*firewall.Mapping{}
nodePortGateway, err := block.GetNodePortGateway()
if err != nil {
return
}
for _, inst := range instances {
if !inst.IsActive() {
continue
}
namespace := vm.GetNamespace(inst.Id, 0)
iface := vm.GetIface(inst.Id, 0)
ifaceHost := vm.GetIfaceHost(inst.Id, 1)
ifaceNodePort := vm.GetIfaceNodePort(inst.Id, 1)
ifaceExternal := vm.GetIfaceExternal(inst.Id, 0)
addr := ""
addr6 := ""
pubAddr := ""
pubAddr6 := ""
if len(inst.PrivateIps) != 0 {
addr = inst.PrivateIps[0]
}
if len(inst.PrivateIps6) != 0 {
addr6 = inst.PrivateIps6[0]
}
if len(inst.PublicIps) != 0 {
pubAddr = inst.PublicIps[0]
}
if len(inst.PublicIps6) != 0 {
pubAddr6 = inst.PublicIps6[0]
} else if len(inst.CloudPublicIps6) != 0 {
pubAddr6 = inst.CloudPublicIps6[0]
}
nodePortAddr := ""
if len(inst.NodePortIps) != 0 {
nodePortAddr = inst.NodePortIps[0]
}
hostNodePortMappings[nodePortAddr] = firewallMaps[namespace]
cloudAddr := ""
cloudIface := vm.GetIfaceCloud(inst.Id, 0)
if len(inst.CloudPrivateIps) != 0 {
cloudAddr = inst.CloudPrivateIps[0]
}
_, ok := state.Interfaces[namespace+"-"+iface]
if ok {
logrus.WithFields(logrus.Fields{
"namespace": namespace,
"interface": iface,
}).Error("iptables: Virtual interface conflict")
continue
}
ingress := firewalls[namespace]
if ingress == nil {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"namespace": namespace,
}).Warn("iptables: Failed to load instance firewall rules")
continue
}
dhcp := false
dhcp6 := false
if nodeNetworkMode == node.Dhcp {
dhcp = true
}
if nodeNetworkMode6 == node.Dhcp {
dhcp6 = true
}
nat6 := false
if nodeNetworkMode != node.Disabled &&
nodeNetworkMode != node.Cloud {
if nodeNetworkMode6 != node.Disabled &&
nodeNetworkMode6 != node.Cloud {
nat6 = true
}
rules := generateInternal(namespace, ifaceExternal,
true, nat6, dhcp, dhcp6, addr, pubAddr, addr6, pubAddr6,
ingress)
state.Interfaces[namespace+"-"+ifaceExternal] = rules
} else if nodeNetworkMode6 != node.Disabled &&
nodeNetworkMode6 != node.Cloud {
rules := generateInternal(namespace, ifaceExternal,
false, true, dhcp, dhcp6, addr, pubAddr, addr6, pubAddr6,
ingress)
state.Interfaces[namespace+"-"+ifaceExternal] = rules
}
if nodeNetworkMode == node.Cloud {
if nodeNetworkMode6 == node.Cloud {
nat6 = true
}
rules := generateInternal(namespace, cloudIface,
true, nat6, false, false, addr, cloudAddr, addr6, pubAddr6,
ingress)
state.Interfaces[namespace+"-"+cloudIface] = rules
}
rules := generateInternal(namespace, ifaceHost,
false, false, false, false, "", "", "", "", ingress)
state.Interfaces[namespace+"-"+ifaceHost] = rules
if nodePortNetwork {
rules := generateNodePort(namespace, ifaceNodePort,
addr, nodePortGateway, firewallMaps[namespace])
state.Interfaces[namespace+"-"+ifaceNodePort] = rules
}
rules = generateVirt(vpcsMap[inst.Vpc], namespace, iface, addr,
addr6, !inst.SkipSourceDestCheck, ingress)
state.Interfaces[namespace+"-"+iface] = rules
}
if nodeFirewall != nil {
state.Interfaces["0-host"] = generateHost("0", "host",
!nodeSelf.NoNodePortNetwork, nodePortGateway,
nodeSelf.DefaultInterface, nodeSelf.ExternalInterfaces,
nodeSelf.PublicIps, nodeFirewall, hostNodePortMappings)
} else if !nodeSelf.NoNodePortNetwork {
state.Interfaces["0-host"] = generateHostNodePort("0", "host",
nodePortGateway, nodeSelf.DefaultInterface,
nodeSelf.ExternalInterfaces, nodeSelf.PublicIps,
hostNodePortMappings)
}
return
}
================================================
FILE: iptables/update.go
================================================
package iptables
import (
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/ipvs"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
type Update struct {
OldState *State
NewState *State
Namespaces []string
FailedNamespaces set.Set
}
func (u *Update) Apply() {
changed := false
var removed []string
oldIfaces := set.NewSet()
newIfaces := set.NewSet()
namespacesSet := set.NewSet()
for _, namespace := range u.Namespaces {
namespacesSet.Add(namespace)
}
for iface := range u.OldState.Interfaces {
oldIfaces.Add(iface)
}
for iface := range u.NewState.Interfaces {
newIfaces.Add(iface)
}
oldIfaces.Subtract(newIfaces)
for iface := range oldIfaces.Iter() {
removed = append(removed, iface.(string))
err := u.OldState.Interfaces[iface.(string)].Remove(nil)
if err != nil {
logrus.WithFields(logrus.Fields{
"iface": iface,
"error": err,
}).Error("iptables: Failed to delete removed interface iptables")
}
}
if removed != nil {
logrus.WithFields(logrus.Fields{
"ifaces": removed,
}).Info("iptables: Removed iptables")
}
for _, rules := range u.NewState.Interfaces {
if u.FailedNamespaces.Contains(rules.Namespace) {
logrus.WithFields(logrus.Fields{
"namespace": rules.Namespace,
}).Warn("iptables: Skipping failed namespace")
continue
}
if rules.Namespace != "0" &&
!namespacesSet.Contains(rules.Namespace) {
_, err := utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns",
"add", rules.Namespace,
)
if err != nil {
logrus.WithFields(logrus.Fields{
"namespace": rules.Namespace,
"error": err,
}).Error("iptables: Namespace add error")
u.FailedNamespaces.Add(rules.Namespace)
continue
}
}
var diff *RulesDiff
oldRules := u.OldState.Interfaces[rules.Namespace+"-"+rules.Interface]
if oldRules != nil {
diff = diffRules(oldRules, rules)
if diff == nil {
continue
}
if !changed {
changed = true
logrus.WithFields(logrus.Fields{
"ingress": diff.IngressDiff,
"ingress6": diff.Ingress6Diff,
"nats": diff.NatsDiff,
"nats6": diff.Nats6Diff,
"maps": diff.MapsDiff,
"maps6": diff.Maps6Diff,
"holds": diff.HoldsDiff,
"holds6": diff.Holds6Diff,
}).Info("iptables: Updating iptables")
}
if (diff.IngressDiff || diff.Ingress6Diff) &&
rules.Interface != "host" {
err := rules.Hold()
if err != nil {
logrus.WithFields(logrus.Fields{
"namespace": rules.Namespace,
"error": err,
}).Error("iptables: Namespace hold error")
u.FailedNamespaces.Add(rules.Namespace)
continue
}
}
err := oldRules.Remove(diff)
if err != nil {
logrus.WithFields(logrus.Fields{
"namespace": rules.Namespace,
"error": err,
}).Error("iptables: Namespace remove error")
u.FailedNamespaces.Add(rules.Namespace)
continue
}
}
if !changed {
changed = true
logrus.Info("iptables: Updating iptables")
}
err := rules.Apply(diff)
if err != nil {
logrus.WithFields(logrus.Fields{
"namespace": rules.Namespace,
"error": err,
}).Error("iptables: Namespace apply error")
u.FailedNamespaces.Add(rules.Namespace)
continue
}
}
hostIface := u.NewState.Interfaces["0-host"]
if hostIface != nil && hostIface.Ipvs != nil {
err := ipvs.UpdateState(u.NewState.Interfaces["0-host"].Ipvs)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("storage: Failed to update ipvs state")
}
}
return
}
func (u *Update) Recover() {
if u.FailedNamespaces.Contains("0") {
err := RecoverNode()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("deploy: Failed to recover node iptables, retrying")
time.Sleep(10 * time.Second)
}
}
if u.FailedNamespaces.Len() > 0 {
logrus.Error("deploy: Failed to update iptables, " +
"reloading state")
time.Sleep(10 * time.Second)
err := u.reload()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("deploy: Failed to recover iptables")
}
}
}
func (u *Update) reload() (err error) {
db := database.GetDatabase()
defer db.Close()
nodeDatacenter, err := node.Self.GetDatacenter(db)
if err != nil {
return
}
vpcs := []*vpc.Vpc{}
if !nodeDatacenter.IsZero() {
vpcs, err = vpc.GetDatacenter(db, nodeDatacenter)
if err != nil {
return
}
}
namespaces, err := utils.GetNamespaces()
if err != nil {
return
}
instances, err := instance.GetAllVirt(db, &bson.M{
"node": node.Self.Id,
}, nil, nil)
if err != nil {
return
}
specRules, nodePortsMap, err := firewall.GetSpecRulesSlow(
db, node.Self.Id, instances)
if err != nil {
return
}
nodeFirewall, firewalls, firewallMaps, _, err := firewall.GetAllIngress(
db, node.Self, instances, specRules, nodePortsMap)
if err != nil {
return
}
err = Init(namespaces, vpcs, instances, nodeFirewall,
firewalls, firewallMaps)
if err != nil {
return
}
return
}
func ApplyUpdate(newState *State, namespaces []string, recover bool) {
lockId := stateLock.Lock()
update := &Update{
OldState: curState,
NewState: newState,
Namespaces: namespaces,
FailedNamespaces: set.NewSet(),
}
update.Apply()
curState = newState
stateLock.Unlock(lockId)
if recover {
update.Recover()
}
return
}
func UpdateState(nodeSelf *node.Node, vpcs []*vpc.Vpc,
instances []*instance.Instance, namespaces []string,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule,
firewallMaps map[string][]*firewall.Mapping) {
newState := LoadState(nodeSelf, vpcs, instances, nodeFirewall,
firewalls, firewallMaps)
ApplyUpdate(newState, namespaces, false)
return
}
func UpdateStateRecover(nodeSelf *node.Node, vpcs []*vpc.Vpc,
instances []*instance.Instance, namespaces []string,
nodeFirewall []*firewall.Rule, firewalls map[string][]*firewall.Rule,
firewallMaps map[string][]*firewall.Mapping) {
newState := LoadState(nodeSelf, vpcs, instances, nodeFirewall,
firewalls, firewallMaps)
ApplyUpdate(newState, namespaces, true)
return
}
================================================
FILE: iptables/utils.go
================================================
package iptables
import (
"fmt"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
func diffCmd(a, b []string) bool {
if len(a) != len(b) {
return true
}
for i := range a {
if a[i] != b[i] {
return true
}
}
return false
}
func diffRules(a, b *Rules) *RulesDiff {
diff := &RulesDiff{}
changed := false
if len(a.Header) != len(b.Header) {
diff.HeaderDiff = true
changed = true
} else {
for i := range a.Header {
if diffCmd(a.Header[i], b.Header[i]) {
diff.HeaderDiff = true
changed = true
break
}
}
}
if len(a.Header6) != len(b.Header6) {
diff.Header6Diff = true
changed = true
} else {
for i := range a.Header6 {
if diffCmd(a.Header6[i], b.Header6[i]) {
diff.Header6Diff = true
changed = true
break
}
}
}
if len(a.SourceDestCheck) != len(b.SourceDestCheck) {
diff.SourceDestCheckDiff = true
changed = true
} else {
for i := range a.SourceDestCheck {
if diffCmd(a.SourceDestCheck[i], b.SourceDestCheck[i]) {
diff.SourceDestCheckDiff = true
changed = true
break
}
}
}
if len(a.SourceDestCheck6) != len(b.SourceDestCheck6) {
diff.SourceDestCheck6Diff = true
changed = true
} else {
for i := range a.SourceDestCheck6 {
if diffCmd(a.SourceDestCheck6[i], b.SourceDestCheck6[i]) {
diff.SourceDestCheck6Diff = true
changed = true
break
}
}
}
if len(a.Ingress) != len(b.Ingress) {
diff.IngressDiff = true
changed = true
} else {
for i := range a.Ingress {
if diffCmd(a.Ingress[i], b.Ingress[i]) {
diff.IngressDiff = true
changed = true
break
}
}
}
if len(a.Ingress6) != len(b.Ingress6) {
diff.Ingress6Diff = true
changed = true
} else {
for i := range a.Ingress6 {
if diffCmd(a.Ingress6[i], b.Ingress6[i]) {
diff.Ingress6Diff = true
changed = true
break
}
}
}
if len(a.Nats) != len(b.Nats) {
diff.NatsDiff = true
changed = true
} else {
for i := range a.Nats {
if diffCmd(a.Nats[i], b.Nats[i]) {
diff.NatsDiff = true
changed = true
break
}
}
}
if len(a.Nats6) != len(b.Nats6) {
diff.Nats6Diff = true
changed = true
} else {
for i := range a.Nats6 {
if diffCmd(a.Nats6[i], b.Nats6[i]) {
diff.Nats6Diff = true
changed = true
break
}
}
}
if len(a.Maps) != len(b.Maps) {
diff.MapsDiff = true
changed = true
} else {
for i := range a.Maps {
if diffCmd(a.Maps[i], b.Maps[i]) {
diff.MapsDiff = true
changed = true
break
}
}
}
if len(a.Maps6) != len(b.Maps6) {
diff.Maps6Diff = true
changed = true
} else {
for i := range a.Maps6 {
if diffCmd(a.Maps6[i], b.Maps6[i]) {
diff.Maps6Diff = true
changed = true
break
}
}
}
if len(a.Holds) != len(b.Holds) {
diff.HoldsDiff = true
changed = true
} else {
for i := range a.Holds {
if diffCmd(a.Holds[i], b.Holds[i]) {
diff.HoldsDiff = true
changed = true
break
}
}
}
if len(a.Holds6) != len(b.Holds6) {
diff.Holds6Diff = true
changed = true
} else {
for i := range a.Holds6 {
if diffCmd(a.Holds6[i], b.Holds6[i]) {
diff.Holds6Diff = true
changed = true
break
}
}
}
if !changed {
return nil
}
return diff
}
func getIptablesCmd(ipv6 bool) string {
if ipv6 {
return "ip6tables"
} else {
return "iptables"
}
}
func loadIptables(namespace, instIface string, state *State,
ipv6 bool) (err error) {
Lock()
defer Unlock()
iptablesCmd := getIptablesCmd(ipv6)
output := ""
if namespace == "0" {
output, err = utils.ExecOutput("", iptablesCmd, "-S")
if err != nil {
return
}
} else {
output, err = utils.ExecOutput("",
"ip", "netns", "exec", namespace, iptablesCmd, "-S")
if err != nil {
return
}
}
for _, line := range strings.Split(output, "\n") {
ruleComment := strings.Contains(line, "pritunl_cloud_rule")
holdComment := strings.Contains(line, "pritunl_cloud_hold")
headComment := strings.Contains(line, "pritunl_cloud_head")
sdcComment := strings.Contains(line, "pritunl_cloud_sdc")
if !ruleComment && !holdComment && !headComment && !sdcComment {
continue
}
cmd := strings.Fields(line)
if len(cmd) < 3 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables state")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables state"),
}
return
}
cmd = cmd[1:]
iface := ""
if sdcComment {
if cmd[0] != "FORWARD" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables sdc chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables sdc chain"),
}
return
}
for i, item := range cmd {
if item == "--physdev-in" || item == "--physdev-out" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables sdc interface")
err = &errortypes.ParseError{
errors.New(
"iptables: Invalid iptables sdc interface"),
}
return
}
iface = strings.Trim(cmd[i+1], "+")
break
}
}
} else if namespace != "0" {
if cmd[0] != "FORWARD" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables chain"),
}
return
}
for i, item := range cmd {
if item == "--physdev-out" || item == "-o" || item == "-i" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables interface")
err = &errortypes.ParseError{
errors.New(
"iptables: Invalid iptables interface"),
}
return
}
iface = strings.Trim(cmd[i+1], "+")
break
}
}
} else {
iface = "host"
if cmd[0] != "INPUT" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables chain"),
}
return
}
}
if iface == "" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Missing iptables interface")
err = &errortypes.ParseError{
errors.New("iptables: Missing iptables interface"),
}
return
}
rules := state.Interfaces[namespace+"-"+iface]
if rules == nil {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
state.Interfaces[namespace+"-"+iface] = rules
}
if holdComment {
if ipv6 {
rules.Holds6 = append(rules.Holds6, cmd)
} else {
rules.Holds = append(rules.Holds, cmd)
}
} else if sdcComment {
if ipv6 {
rules.SourceDestCheck6 = append(rules.SourceDestCheck6, cmd)
} else {
rules.SourceDestCheck = append(rules.SourceDestCheck, cmd)
}
} else {
if headComment {
if ipv6 {
rules.Header6 = append(rules.Header6, cmd)
} else {
rules.Header = append(rules.Header, cmd)
}
} else {
if ipv6 {
rules.Ingress6 = append(rules.Ingress6, cmd)
} else {
rules.Ingress = append(rules.Ingress, cmd)
}
}
}
}
if namespace == "0" {
output, err = utils.ExecOutput("", iptablesCmd, "-S", "-t", "nat")
if err != nil {
return
}
} else {
output, err = utils.ExecOutput("",
"ip", "netns", "exec", namespace, iptablesCmd, "-S", "-t", "nat")
if err != nil {
return
}
}
postIface := ""
natRules := [][]string{}
for _, line := range strings.Split(output, "\n") {
natComment := strings.Contains(line, "pritunl_cloud_nat")
mapComment := strings.Contains(line, "pritunl_cloud_map")
if !natComment && !mapComment {
continue
}
cmd := strings.Fields(line)
if len(cmd) < 3 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables state")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables state"),
}
return
}
cmd = cmd[1:]
if mapComment && namespace == "0" {
if cmd[0] != "PREROUTING" && cmd[0] != "POSTROUTING" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables map chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables map chain"),
}
return
}
rules := state.Interfaces[namespace+"-host"]
if rules == nil {
rules = &Rules{
Namespace: namespace,
Interface: "host",
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
state.Interfaces[namespace+"-host"] = rules
}
if ipv6 {
rules.Maps6 = append(rules.Maps6, cmd)
} else {
rules.Maps = append(rules.Maps, cmd)
}
} else if mapComment {
iface := instIface
for i, item := range cmd {
if item == "-i" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables interface")
err = &errortypes.ParseError{
errors.New(
"iptables: Invalid iptables interface"),
}
return
}
iface = strings.Trim(cmd[i+1], "+")
break
}
}
if iface == "" {
logrus.WithFields(logrus.Fields{
"namespace": namespace,
"iface": iface,
"iptables_rule": line,
}).Error("iptables: Missing instance iface for map")
} else {
if cmd[0] != "PREROUTING" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables map chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables map chain"),
}
return
}
rules := state.Interfaces[namespace+"-"+iface]
if rules == nil {
rules = &Rules{
Namespace: namespace,
Interface: iface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Maps: [][]string{},
Maps6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
state.Interfaces[namespace+"-"+iface] = rules
}
if ipv6 {
rules.Maps6 = append(rules.Maps6, cmd)
} else {
rules.Maps = append(rules.Maps, cmd)
}
}
} else if natComment {
if cmd[0] != "PREROUTING" && cmd[0] != "POSTROUTING" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables map chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables map chain"),
}
return
}
if cmd[0] == "POSTROUTING" {
for i, item := range cmd {
if item == "-o" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables addr")
err = &errortypes.ParseError{
errors.New(
"iptables: Invalid iptables addr"),
}
return
}
postIface = strings.Trim(cmd[i+1], "+")
}
}
}
natRules = append(natRules, cmd)
}
}
cloudPostIface := ""
cloudNatRules := [][]string{}
for _, line := range strings.Split(output, "\n") {
if !strings.Contains(line, "pritunl_cloud_cloud_nat") {
continue
}
cmd := strings.Fields(line)
if len(cmd) < 3 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables state")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables state"),
}
return
}
cmd = cmd[1:]
if cmd[0] != "PREROUTING" && cmd[0] != "POSTROUTING" {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables map chain")
err = &errortypes.ParseError{
errors.New("iptables: Invalid iptables map chain"),
}
return
}
if cmd[0] == "POSTROUTING" {
for i, item := range cmd {
if item == "-o" {
if len(cmd) < i+2 {
logrus.WithFields(logrus.Fields{
"iptables_rule": line,
}).Error("iptables: Invalid iptables addr")
err = &errortypes.ParseError{
errors.New(
"iptables: Invalid iptables addr"),
}
return
}
cloudPostIface = strings.Trim(cmd[i+1], "+")
}
}
}
cloudNatRules = append(cloudNatRules, cmd)
}
if postIface != "" {
rules := state.Interfaces[namespace+"-"+postIface]
if rules == nil {
rules = &Rules{
Namespace: namespace,
Interface: postIface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
state.Interfaces[namespace+"-"+postIface] = rules
}
if ipv6 {
rules.Nats6 = append(rules.Nats6, natRules...)
} else {
rules.Nats = append(rules.Nats, natRules...)
}
}
if cloudPostIface != "" {
rules := state.Interfaces[namespace+"-"+cloudPostIface]
if rules == nil {
rules = &Rules{
Namespace: namespace,
Interface: cloudPostIface,
Header: [][]string{},
Header6: [][]string{},
SourceDestCheck: [][]string{},
SourceDestCheck6: [][]string{},
Ingress: [][]string{},
Ingress6: [][]string{},
Holds: [][]string{},
Holds6: [][]string{},
}
state.Interfaces[namespace+"-"+cloudPostIface] = rules
}
if ipv6 {
rules.Nats6 = append(rules.Nats6, cloudNatRules...)
} else {
rules.Nats = append(rules.Nats, cloudNatRules...)
}
}
return
}
func RecoverNode() (err error) {
cmds := [][]string{}
if !node.Self.Firewall {
return
}
cmds = append(cmds, []string{
"-I", "INPUT", "1",
"-m", "comment",
"--comment", "pritunl_cloud_rule",
"-j", "DROP",
})
cmds = append(cmds, []string{
"-I", "INPUT", "1",
"-m", "conntrack",
"--ctstate", "INVALID",
"-m", "comment",
"--comment", "pritunl_cloud_rule",
"-j", "DROP",
})
cmds = append(cmds, []string{
"-I", "INPUT", "1",
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
"-m", "comment", "--comment", "pritunl_cloud_rule",
"-j", "ACCEPT",
})
for _, cmd := range cmds {
Lock()
output, e := utils.ExecCombinedOutput("", "iptables", cmd...)
Unlock()
if e != nil {
err = e
logrus.WithFields(logrus.Fields{
"command": cmd,
"output": output,
"error": err,
}).Error("iptables: Failed to add iptables recover rule")
return
}
}
for _, cmd := range cmds {
Lock()
output, e := utils.ExecCombinedOutput("", "ip6tables", cmd...)
Unlock()
if e != nil {
err = e
logrus.WithFields(logrus.Fields{
"command": cmd,
"output": output,
"error": err,
}).Error("iptables: Failed to add ip6tables recover rule")
return
}
}
return
}
func Init(namespaces []string, vpcs []*vpc.Vpc,
instances []*instance.Instance, nodeFirewall []*firewall.Rule,
firewalls map[string][]*firewall.Rule,
firewallMaps map[string][]*firewall.Mapping) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w", "net.ipv6.conf.all.accept_ra=2",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w", "net.ipv6.conf.default.accept_ra=2",
)
if err != nil {
return
}
interfaces, err := utils.GetInterfaces()
if err != nil {
return
}
for _, iface := range interfaces {
if len(iface) == 14 && (strings.HasPrefix(iface, "v") ||
strings.HasPrefix(iface, "x")) {
continue
}
utils.ExecCombinedOutput("",
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.accept_ra=2", iface),
)
}
utils.ExecCombinedOutput(
"", "sysctl", "-w", "net.bridge.bridge-nf-call-iptables=1",
)
utils.ExecCombinedOutput(
"", "sysctl", "-w", "net.bridge.bridge-nf-call-ip6tables=1",
)
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w", "net.ipv4.ip_forward=1",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w", "net.ipv6.conf.all.forwarding=1",
)
if err != nil {
return
}
state := &State{
Interfaces: map[string]*Rules{},
}
err = loadIptables("0", "", state, false)
if err != nil {
return
}
err = loadIptables("0", "", state, true)
if err != nil {
return
}
namespaceMap := map[string]*instance.Instance{}
for _, inst := range instances {
namespaceMap[vm.GetNamespace(inst.Id, 0)] = inst
}
for _, namespace := range namespaces {
instIface := ""
inst := namespaceMap[namespace]
if inst != nil {
instIface = vm.GetIface(inst.Id, 0)
}
err = loadIptables(namespace, instIface, state, false)
if err != nil {
return
}
err = loadIptables(namespace, instIface, state, true)
if err != nil {
return
}
}
curState = state
UpdateState(node.Self, vpcs, instances,
namespaces, nodeFirewall, firewalls, firewallMaps)
return
}
func protocolIndex(proto string) string {
switch proto {
case "icmp":
return "1"
case "tcp":
return "6"
case "udp":
return "17"
default:
return "0"
}
}
================================================
FILE: ipvs/constants.go
================================================
package ipvs
const (
Tcp = "-t"
Udp = "-u"
RoundRobin = "rr"
)
================================================
FILE: ipvs/ipvs.go
================================================
package ipvs
import (
"fmt"
"sort"
"strconv"
"strings"
"time"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
var (
curState *State
)
type State struct {
Services map[string]*Service
}
func (s *State) Print() string {
var output strings.Builder
output.WriteString("IPVS Configuration:\n")
output.WriteString("=================\n\n")
if len(s.Services) == 0 {
output.WriteString("No services configured.\n")
return output.String()
}
serviceKeys := make([]string, 0, len(s.Services))
for key := range s.Services {
serviceKeys = append(serviceKeys, key)
}
sort.Strings(serviceKeys)
for _, key := range serviceKeys {
service := s.Services[key]
output.WriteString(fmt.Sprintf("Service: %s%s\n", service.Protocol, service.Key()))
output.WriteString(fmt.Sprintf(" Key: %s\n", key))
output.WriteString(fmt.Sprintf(" Scheduler: %s\n", service.Scheduler))
if len(service.Targets) == 0 {
output.WriteString(" No targets configured.\n")
} else {
output.WriteString(" Targets:\n")
sort.Slice(service.Targets, func(i, j int) bool {
if service.Targets[i].Address != service.Targets[j].Address {
return service.Targets[i].Address < service.Targets[j].Address
}
return service.Targets[i].Port < service.Targets[j].Port
})
for _, target := range service.Targets {
masq := "No"
if target.Masquerade {
masq = "Yes"
}
output.WriteString(fmt.Sprintf(" - %s (Weight: %d, Masquerade: %s)\n",
target.Key(), target.Weight, masq))
}
}
output.WriteString("\n")
}
return output.String()
}
func (s *State) AddTarget(serviceAddr, targetAddr string,
port int, protocol string) (err error) {
serviceKey := fmt.Sprintf("%s%s:%d", protocol, serviceAddr, port)
service := s.Services[serviceKey]
if service == nil {
service = &Service{
Scheduler: RoundRobin,
Protocol: protocol,
Address: serviceAddr,
Port: port,
}
s.Services[serviceKey] = service
}
target := &Target{
Service: service,
Address: targetAddr,
Port: port,
Weight: 1,
Masquerade: true,
}
service.Targets = append(service.Targets, target)
return
}
func UpdateState(newState *State) (err error) {
updated := false
if curState == nil {
var state *State
state, err = LoadState()
if err != nil {
return
}
curState = state
}
for serviceKey, service := range curState.Services {
newService := newState.Services[serviceKey]
if newService == nil {
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "unknown_service",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = service.Delete()
if err != nil {
return
}
continue
}
for _, target := range service.Targets {
found := false
for _, newTarget := range newService.Targets {
if target.Address == newTarget.Address &&
target.Port == newTarget.Port {
if target.Weight != newTarget.Weight ||
target.Masquerade != newTarget.Masquerade {
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "weight_masquerade",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = target.Delete()
if err != nil {
return
}
found = false
} else {
found = true
}
break
}
}
if !found {
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "target_unknown",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = target.Delete()
if err != nil {
return
}
}
}
if service.Scheduler != newService.Scheduler {
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "scheduler",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = service.Delete()
if err != nil {
return
}
err = newService.Add()
if err != nil {
return
}
for _, target := range newService.Targets {
target.Service = newService
err = target.Add()
if err != nil {
return
}
}
}
}
for serviceKey, newService := range newState.Services {
service := curState.Services[serviceKey]
if service == nil {
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "new_service",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = newService.Add()
if err != nil {
return
}
for _, target := range newService.Targets {
target.Service = newService
err = target.Add()
if err != nil {
return
}
}
} else if service.Scheduler == newService.Scheduler {
for _, newTarget := range newService.Targets {
found := false
needsUpdate := false
for _, target := range service.Targets {
if target.Address == newTarget.Address &&
target.Port == newTarget.Port {
found = true
if target.Weight != newTarget.Weight ||
target.Masquerade != newTarget.Masquerade {
needsUpdate = true
}
break
}
}
if !found || needsUpdate {
newTarget.Service = service
if !updated {
logrus.WithFields(logrus.Fields{
"reason": "new_target",
}).Info("ipvs: Updating ipvs state")
updated = true
}
err = newTarget.Add()
if err != nil {
return
}
}
}
}
}
curState = newState
return
}
func LoadState() (state *State, err error) {
resp, err := commander.Exec(&commander.Opt{
Name: "ipvsadm-save",
Args: []string{
"-n",
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error("ipvs: Failed to load state")
return
}
state = &State{
Services: map[string]*Service{},
}
for _, line := range strings.Split(string(resp.Output), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
parts := strings.Fields(line)
if len(parts) == 0 {
continue
}
if parts[0] == "-A" {
serviceKey := ""
service := &Service{
Targets: []*Target{},
}
for i := 0; i < len(parts); i++ {
switch parts[i] {
case "-t":
if i+1 < len(parts) {
serviceKey = parts[i+1]
addrPort := strings.Split(serviceKey, ":")
serviceKey = Tcp + serviceKey
service.Protocol = Tcp
if len(addrPort) == 2 {
service.Address = addrPort[0]
service.Port, _ = strconv.Atoi(addrPort[1])
}
i++
}
case "-u":
if i+1 < len(parts) {
serviceKey = parts[i+1]
addrPort := strings.Split(serviceKey, ":")
serviceKey = Udp + serviceKey
service.Protocol = Udp
if len(addrPort) == 2 {
service.Address = addrPort[0]
service.Port, _ = strconv.Atoi(addrPort[1])
}
i++
}
case "-s":
if i+1 < len(parts) {
switch parts[i+1] {
case "rr":
service.Scheduler = RoundRobin
break
}
i++
}
}
}
if serviceKey != "" {
state.Services[serviceKey] = service
}
} else if parts[0] == "-a" {
target := &Target{}
for i := 0; i < len(parts); i++ {
switch parts[i] {
case "-t":
if i+1 < len(parts) {
target.Service = state.Services[Tcp+parts[i+1]]
i++
}
case "-u":
if i+1 < len(parts) {
target.Service = state.Services[Udp+parts[i+1]]
i++
}
case "-r":
if i+1 < len(parts) {
addrPort := strings.Split(parts[i+1], ":")
if len(addrPort) == 2 {
target.Address = addrPort[0]
target.Port, _ = strconv.Atoi(addrPort[1])
}
i++
}
case "-w":
if i+1 < len(parts) {
target.Weight, _ = strconv.Atoi(parts[i+1])
i++
}
case "-m":
target.Masquerade = true
}
}
if target.Service != nil {
target.Service.Targets = append(target.Service.Targets, target)
}
}
}
return
}
func New() *State {
return &State{
Services: map[string]*Service{},
}
}
================================================
FILE: ipvs/service.go
================================================
package ipvs
import (
"fmt"
"time"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
var (
hasSysctl = false
)
type Service struct {
Scheduler string
Protocol string
Address string
Port int
Targets []*Target
}
func (s *Service) Key() string {
return fmt.Sprintf("%s:%d", s.Address, s.Port)
}
func (s *Service) Add() (err error) {
if s.Scheduler == "" {
s.Scheduler = RoundRobin
}
if !hasSysctl {
resp, err := commander.Exec(&commander.Opt{
Name: "sysctl",
Args: []string{
"-w", "net.ipv4.vs.conntrack=1",
},
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error(
"ipvs: Failed to set ipvs sysctl")
err = nil
}
hasSysctl = true
}
resp, err := commander.Exec(&commander.Opt{
Name: "ipvsadm",
Args: []string{
"-A",
s.Protocol, s.Key(),
"-s", s.Scheduler,
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error("ipvs: Failed to add service")
return
}
return
}
func (s *Service) Delete() (err error) {
resp, err := commander.Exec(&commander.Opt{
Name: "ipvsadm",
Args: []string{
"-D",
s.Protocol, s.Key(),
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error("ipvs: Failed to remove service")
return
}
return
}
================================================
FILE: ipvs/target.go
================================================
package ipvs
import (
"fmt"
"time"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
type Target struct {
Service *Service
Address string
Port int
Weight int
Masquerade bool
}
func (t *Target) Key() string {
return fmt.Sprintf("%s:%d", t.Address, t.Port)
}
func (t *Target) Add() (err error) {
if t.Weight == 0 {
t.Weight = 1
}
args := []string{
"-a",
t.Service.Protocol, t.Service.Key(),
"-r", t.Key(),
}
if t.Masquerade {
args = append(args, "-m")
}
args = append(args, "-w", fmt.Sprintf("%d", t.Weight))
resp, err := commander.Exec(&commander.Opt{
Name: "ipvsadm",
Args: args,
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error("ipvs: Failed to add target")
return
}
return
}
func (t *Target) Delete() (err error) {
resp, err := commander.Exec(&commander.Opt{
Name: "ipvsadm",
Args: []string{
"-d",
t.Service.Protocol, t.Service.Key(),
"-r", t.Key(),
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error("ipvs: Failed to remove target")
return
}
return
}
================================================
FILE: iscsi/iscsi.go
================================================
package iscsi
import (
"fmt"
"net/url"
"strconv"
"strings"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type Device struct {
Host string `bson:"host" json:"host"`
Port int `bson:"port" json:"port"`
Iqn string `bson:"iqn" json:"iqn"`
Lun string `bson:"lun" json:"lun"`
Username string `bson:"username" json:"username"`
Password string `bson:"password" json:"password"`
Uri string `bson:"-" json:"uri"`
}
func (d *Device) Json() {
host := ""
if d.Port != 0 {
host = fmt.Sprintf("%s:%d", d.Host, d.Port)
} else {
host = fmt.Sprintf("%s", d.Host)
}
uri := url.URL{
Scheme: "iscsi",
Host: host,
Path: fmt.Sprintf("%s/%s", d.Iqn, d.Lun),
}
if d.Username != "" && d.Password != "" {
uri.User = url.UserPassword(d.Username, d.Password)
}
d.Uri = uri.String()
}
func (d *Device) QemuUri() (uriStr string) {
host := ""
if d.Port != 0 {
host = fmt.Sprintf("%s:%d", d.Host, d.Port)
} else {
host = fmt.Sprintf("%s", d.Host)
}
uri := url.URL{
Scheme: "iscsi",
Host: host,
Path: fmt.Sprintf("%s/%s", d.Iqn, d.Lun),
}
if d.Username != "" && d.Password != "" {
uri.User = url.UserPassword(d.Username, d.Password)
}
uriStr = uri.String()
uriStr = strings.Replace(uriStr, "%", "", -1)
if d.Username != "" && d.Password != "" && len(uriStr) > 8 {
uriStr = uriStr[:8] + strings.Replace(uriStr[8:], ":", "%%", 1)
}
return
}
func (d *Device) Parse() (errData *errortypes.ErrorData, err error) {
if d.Uri == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_uri",
Message: "Invalid iSCSI URI",
}
return
}
uri, err := url.Parse(d.Uri)
if err != nil {
err = nil
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_uri",
Message: "Invalid iSCSI URI",
}
return
}
if uri.Scheme != "iscsi" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_uri",
Message: "Invalid iSCSI URI",
}
return
}
port := 0
portStr := uri.Port()
if portStr != "" {
port, err = strconv.Atoi(portStr)
if err != nil {
err = nil
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_port",
Message: "Invalid iSCSI port",
}
return
}
}
host := uri.Hostname()
if host == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_uri",
Message: "Invalid iSCSI URI",
}
return
}
username := ""
password := ""
if uri.User != nil {
username = uri.User.Username()
password, _ = uri.User.Password()
if username != "" || password != "" {
if username == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_username",
Message: "Missing iSCSI username",
}
return
}
if password == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_password",
Message: "Missing iSCSI password",
}
return
}
}
}
path := strings.Split(uri.Path, "/")
if len(path) != 3 {
errData = &errortypes.ErrorData{
Error: "missing_iscsi_iqn_lun",
Message: "Missing iSCSI IQN and LUN",
}
return
}
iqn := path[1]
if iqn == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_iqn",
Message: "Invalid iSCSI IQN",
}
return
}
lun := path[2]
if lun == "" {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_lun",
Message: "Invalid iSCSI LUN",
}
return
}
d.Host = host
d.Port = port
d.Iqn = iqn
d.Lun = lun
d.Username = username
d.Password = password
d.Uri = ""
d.Json()
if strings.Contains(d.Uri, "%") {
errData = &errortypes.ErrorData{
Error: "invalid_iscsi_uri",
Message: "Invalid iSCSI URI, cannot contain % character",
}
return
}
d.Uri = ""
return
}
================================================
FILE: iso/iso.go
================================================
package iso
import (
"io/ioutil"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var (
syncLast time.Time
syncLock sync.Mutex
syncCache []*Iso
)
type Iso struct {
Name string `bson:"name" json:"name"`
}
func GetIsos(isoDir string) (isos []*Iso, err error) {
if time.Since(syncLast) < 30*time.Second {
isos = syncCache
return
}
syncLock.Lock()
defer syncLock.Unlock()
err = utils.ExistsMkdir(isoDir, 0755)
if err != nil {
return
}
isoFiles, err := ioutil.ReadDir(isoDir)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "backup: Failed to read isos directory"),
}
return
}
for _, item := range isoFiles {
filename := item.Name()
filenameFilt := utils.FilterRelPath(filename)
if filenameFilt != filename {
logrus.WithFields(logrus.Fields{
"name": filename,
}).Error("iso: Invalid ISO filename")
}
iso := &Iso{
Name: filenameFilt,
}
isos = append(isos, iso)
}
syncCache = isos
syncLast = time.Now()
return
}
================================================
FILE: journal/constants.go
================================================
package journal
const (
InstanceAgent = 1
DeploymentAgent = 2
)
const (
Panic = 1
Critical = 2
Error = 3
Warning = 4
Info = 5
Debug = 6
Trace = 7
)
================================================
FILE: journal/journal.go
================================================
package journal
import (
"fmt"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
type Journal struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Resource bson.ObjectID `bson:"r" json:"r"`
Kind int32 `bson:"k" json:"k"`
Level int32 `bson:"l" json:"l"`
Timestamp time.Time `bson:"t" json:"t"`
Count int32 `bson:"c" json:"-"`
Message string `bson:"m" json:"m"`
Fields map[string]string `bson:"f,omitempty" json:"f"`
}
func (j *Journal) String() string {
return fmt.Sprintf(
"[%s] %s\n",
j.Timestamp.Format("2006-01-02 15:04:05"),
j.Message,
)
}
func (j *Journal) Insert(db *database.Database) (err error) {
coll := db.Journal()
if j.Level == 0 {
j.Level = Info
}
_, err = coll.InsertOne(db, j)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: journal/store.go
================================================
package journal
import (
"github.com/pritunl/pritunl-cloud/database"
)
type KindGenerator interface {
GetKind(db *database.Database, key string) (kind int32, err error)
}
================================================
FILE: journal/utils.go
================================================
package journal
import (
"context"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
)
func GetOutput(c context.Context, db *database.Database,
resource bson.ObjectID, kind int32) (output []string, err error) {
coll := db.Journal()
limit := int64(settings.Hypervisor.JournalDisplayLimit)
cursor, err := coll.Find(
c,
&bson.M{
"r": resource,
"k": kind,
},
options.Find().
SetLimit(limit).
SetSort(&bson.D{
{"t", -1},
{"c", -1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(c)
outputRevrse := []string{}
for cursor.Next(c) {
jrnl := &Journal{}
err = cursor.Decode(jrnl)
if err != nil {
err = database.ParseError(err)
return
}
outputRevrse = append(outputRevrse, jrnl.String())
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
for i := len(outputRevrse) - 1; i >= 0; i-- {
if i == 0 {
output = append(output, strings.TrimSuffix(outputRevrse[i], "\n"))
} else {
output = append(output, outputRevrse[i])
}
}
return
}
func Remove(db *database.Database, resource bson.ObjectID,
kind int) (err error) {
coll := db.Journal()
_, err = coll.DeleteMany(db, &bson.M{
"r": resource,
"k": kind,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveAll(db *database.Database, resource bson.ObjectID) (err error) {
coll := db.Journal()
_, err = coll.DeleteMany(db, &bson.M{
"r": resource,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
================================================
FILE: lock/lvm.go
================================================
package lock
import (
"fmt"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
)
type LvmLocker struct {
Id string `bson:"_id" json:"_id"`
Node bson.ObjectID `bson:"node" json:"node"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
}
func LvmLock(db *database.Database, vgName, lvName string) (
acquired bool, err error) {
coll := db.LvmLock()
doc := &LvmLocker{
Id: fmt.Sprintf("%s/%s", vgName, lvName),
Node: node.Self.Id,
Timestamp: time.Now(),
}
_, err = coll.InsertOne(db, doc)
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.DuplicateKeyError); ok {
err = nil
return
}
return
}
acquired = true
return
}
func LvmRelock(db *database.Database, vgName, lvName string) (err error) {
coll := db.LvmLock()
_, err = coll.UpdateOne(db, &bson.M{
"id": fmt.Sprintf("%s/%s", vgName, lvName),
"node": node.Self.Id,
}, &bson.M{
"timestamp": time.Now(),
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func LvmUnlock(db *database.Database, vgName, lvName string) (err error) {
coll := db.LvmLock()
_, err = coll.DeleteOne(db, &bson.M{
"id": fmt.Sprintf("%s/%s", vgName, lvName),
"node": node.Self.Id,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: log/constants.go
================================================
package log
const (
Debug = "debug"
Info = "info"
Warning = "warning"
Error = "error"
Fatal = "fatal"
Panic = "panic"
Unknown = "unknown"
)
================================================
FILE: log/log.go
================================================
package log
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/requires"
)
var published = false
type Entry struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Level string `bson:"level" json:"level"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Message string `bson:"message" json:"message"`
Stack string `bson:"stack" json:"stack"`
Fields map[string]interface{} `bson:"fields" json:"fields"`
}
func (e *Entry) Insert(db *database.Database) (err error) {
coll := db.Logs()
if !e.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("log: Entry already exists"),
}
return
}
_, err = coll.InsertOne(db, e)
if err != nil {
err = database.ParseError(err)
return
}
published = true
return
}
func publish() {
db := database.GetDatabase()
defer db.Close()
event.PublishDispatch(db, "log.change")
}
func initSender() {
for {
time.Sleep(1500 * time.Millisecond)
if published {
published = false
publish()
}
}
}
func init() {
module := requires.New("log")
module.After("logger")
module.Handler = func() (err error) {
go initSender()
return
}
}
================================================
FILE: log/utils.go
================================================
package log
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, logId bson.ObjectID) (
entry *Entry, err error) {
coll := db.Logs()
entry = &Entry{}
err = coll.FindOneId(logId, entry)
if err != nil {
return
}
return
}
func GetAll(db *database.Database, query *bson.M, page, pageCount int64) (
entries []*Entry, count int64, err error) {
coll := db.Logs()
entries = []*Entry{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"$natural", -1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
entry := &Entry{}
err = cursor.Decode(entry)
if err != nil {
err = database.ParseError(err)
return
}
entries = append(entries, entry)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Clear(db *database.Database) (err error) {
coll := db.Logs()
_, err = coll.DeleteMany(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
event.PublishDispatch(db, "log.change")
return
}
================================================
FILE: logger/database.go
================================================
package logger
import (
"fmt"
"strings"
"github.com/sirupsen/logrus"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/log"
)
var (
databaseBuffer = make(chan *logrus.Entry, 128)
)
type databaseSender struct{}
func (s *databaseSender) Init() {}
func (s *databaseSender) Parse(entry *logrus.Entry) {
if len(buffer) <= 32 {
databaseBuffer <- entry
}
}
func databaseSend(entry *logrus.Entry) (err error) {
level := ""
db := database.GetDatabase()
if db == nil {
return
}
defer db.Close()
switch entry.Level {
case logrus.DebugLevel:
level = log.Debug
break
case logrus.WarnLevel:
level = log.Warning
break
case logrus.InfoLevel:
level = log.Info
break
case logrus.ErrorLevel:
level = log.Error
break
case logrus.FatalLevel:
level = log.Fatal
break
case logrus.PanicLevel:
level = log.Panic
break
default:
level = log.Unknown
}
ent := &log.Entry{
Level: level,
Timestamp: entry.Time,
Message: entry.Message,
Fields: map[string]interface{}{},
}
for key, val := range entry.Data {
if key == "error" {
ent.Stack = fmt.Sprintf("%s", val)
} else {
ent.Fields[key] = val
}
}
err = ent.Insert(db)
if err != nil {
return
}
return
}
func initDatabaseSender() {
go func() {
for {
entry := <-databaseBuffer
if constants.Interrupt {
return
}
if strings.HasPrefix(entry.Message, "logger:") {
continue
}
err := databaseSend(entry)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("logger: Database send error")
}
}
}()
}
func init() {
senders = append(senders, &databaseSender{})
}
================================================
FILE: logger/file.go
================================================
package logger
import (
"os"
"github.com/sirupsen/logrus"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type fileSender struct{}
func (s *fileSender) Init() {}
func (s *fileSender) Parse(entry *logrus.Entry) {
err := s.send(entry)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("logger: File send error")
}
}
func (s *fileSender) send(entry *logrus.Entry) (err error) {
msg := formatPlain(entry)
file, err := os.OpenFile(constants.LogPath,
os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "logger: Failed to open log file"),
}
return
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "logger: Failed to stat log file"),
}
return
}
if stat.Size() >= 5000000 {
os.Remove(constants.LogPath2)
err = os.Rename(constants.LogPath, constants.LogPath2)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "logger: Failed to rotate log file"),
}
return
}
file.Close()
file, err = os.OpenFile(constants.LogPath,
os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "logger: Failed to open log file"),
}
return
}
}
_, err = file.Write(msg)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "logger: Failed to write to log file"),
}
return
}
return
}
func init() {
senders = append(senders, &fileSender{})
}
================================================
FILE: logger/formatter.go
================================================
package logger
import (
"fmt"
"reflect"
"sort"
"time"
"github.com/pritunl/pritunl-cloud/colorize"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
var (
blueArrow = colorize.ColorString("▶", colorize.BlueBold, colorize.None)
whiteDiamond = colorize.ColorString("◆", colorize.WhiteBold, colorize.None)
)
func format(entry *logrus.Entry) (output []byte) {
msg := fmt.Sprintf("%s%s %s %s",
formatTime(entry.Time),
formatLevel(entry.Level),
blueArrow,
entry.Message,
)
keys := []string{}
var errStr string
for key, val := range entry.Data {
if key == "error" {
errStr = fmt.Sprintf("%s", val)
continue
} else if key == "error_data" {
if val != nil && !reflect.ValueOf(val).IsNil() {
if errData, ok := val.(*errortypes.ErrorData); ok {
entry.Data["error_key"] = errData.Error
entry.Data["error_msg"] = errData.Message
keys = append(keys, "error_key", "error_msg")
}
}
continue
}
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
msg += fmt.Sprintf(" %s %s=%v", whiteDiamond,
colorize.ColorString(key, colorize.CyanBold, colorize.None),
colorize.ColorString(fmt.Sprintf("%#v", entry.Data[key]),
colorize.GreenBold, colorize.None))
}
if errStr != "" {
msg += "\n" + colorize.ColorString(errStr, colorize.Red, colorize.None)
}
if string(msg[len(msg)-1]) != "\n" {
msg += "\n"
}
output = []byte(msg)
return
}
func formatPlain(entry *logrus.Entry) (output []byte) {
msg := fmt.Sprintf("%s%s ▶ %s",
entry.Time.Format("[2006-01-02 15:04:05]"),
formatLevelPlain(entry.Level),
entry.Message,
)
keys := []string{}
var errStr string
for key, val := range entry.Data {
if key == "error" {
errStr = fmt.Sprintf("%s", val)
continue
} else if key == "error_data" {
if val != nil && !reflect.ValueOf(val).IsNil() {
if errData, ok := val.(*errortypes.ErrorData); ok {
entry.Data["error_key"] = errData.Error
entry.Data["error_msg"] = errData.Message
keys = append(keys, "error_key", "error_msg")
}
}
continue
}
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
msg += fmt.Sprintf(" ◆ %s=%v", key,
fmt.Sprintf("%#v", entry.Data[key]))
}
if errStr != "" {
msg += "\n" + errStr
}
if string(msg[len(msg)-1]) != "\n" {
msg += "\n"
}
output = []byte(msg)
return
}
func formatTime(timestamp time.Time) (str string) {
return colorize.ColorString(
timestamp.Format("[2006-01-02 15:04:05]"),
colorize.Bold,
colorize.None,
)
}
func formatLevel(lvl logrus.Level) (str string) {
var colorBg colorize.Color
switch lvl {
case logrus.InfoLevel:
colorBg = colorize.CyanBg
str = "[INFO]"
case logrus.WarnLevel:
colorBg = colorize.YellowBg
str = "[WARN]"
case logrus.ErrorLevel:
colorBg = colorize.RedBg
str = "[ERRO]"
case logrus.FatalLevel:
colorBg = colorize.RedBg
str = "[FATL]"
case logrus.PanicLevel:
colorBg = colorize.RedBg
str = "[PANC]"
default:
colorBg = colorize.BlackBg
}
str = colorize.ColorString(str, colorize.WhiteBold, colorBg)
return
}
func formatLevelPlain(lvl logrus.Level) string {
switch lvl {
case logrus.InfoLevel:
return "[INFO]"
case logrus.WarnLevel:
return "[WARN]"
case logrus.ErrorLevel:
return "[ERRO]"
case logrus.FatalLevel:
return "[FATL]"
case logrus.PanicLevel:
return "[PANC]"
default:
}
return ""
}
type formatter struct{}
func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {
return format(entry), nil
}
================================================
FILE: logger/hook.go
================================================
package logger
import (
"strings"
"github.com/sirupsen/logrus"
)
type logHook struct{}
func (h *logHook) Fire(entry *logrus.Entry) (err error) {
if strings.HasPrefix(entry.Message, "logger:") {
return
}
if len(buffer) <= 32 {
buffer <- entry
}
return
}
func (h *logHook) Levels() []logrus.Level {
return []logrus.Level{
logrus.InfoLevel,
logrus.WarnLevel,
logrus.ErrorLevel,
logrus.FatalLevel,
logrus.PanicLevel,
}
}
================================================
FILE: logger/limiter.go
================================================
package logger
import (
"hash/fnv"
"time"
"github.com/sirupsen/logrus"
)
type limiter map[uint32]time.Time
func (l limiter) Check(entry *logrus.Entry, limit time.Duration) bool {
hash := fnv.New32a()
hash.Write([]byte(entry.Message))
key := hash.Sum32()
if timestamp, ok := l[key]; ok &&
time.Since(timestamp) < limit {
return false
}
l[key] = time.Now()
return true
}
================================================
FILE: logger/logger.go
================================================
package logger
import (
"os"
"strings"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/sirupsen/logrus"
)
var (
buffer = make(chan *logrus.Entry, 128)
senders = []sender{}
)
func initSender() {
for _, sndr := range senders {
sndr.Init()
}
go func() {
for {
entry := <-buffer
if constants.Interrupt {
return
}
if strings.HasPrefix(entry.Message, "logger:") {
continue
}
for _, sndr := range senders {
sndr.Parse(entry)
}
}
}()
}
func Init() {
logrus.SetFormatter(&formatter{})
logrus.AddHook(&logHook{})
logrus.SetOutput(os.Stderr)
logrus.SetLevel(logrus.InfoLevel)
}
func InitStdout() {
logrus.SetFormatter(&formatter{})
logrus.SetOutput(os.Stdout)
logrus.SetLevel(logrus.InfoLevel)
}
func init() {
module := requires.New("logger")
module.After("config")
module.Handler = func() (err error) {
initSender()
initDatabaseSender()
return
}
}
================================================
FILE: logger/sender.go
================================================
package logger
import (
"github.com/sirupsen/logrus"
)
type sender interface {
Init()
Parse(entry *logrus.Entry)
}
================================================
FILE: logger/writer.go
================================================
package logger
import (
"strings"
"github.com/sirupsen/logrus"
)
type ErrorWriter struct {
Message string
Fields logrus.Fields
Filters []string
}
func (w *ErrorWriter) Write(input []byte) (n int, err error) {
n = len(input)
inputStr := string(input)
if w.Filters != nil {
for _, filter := range w.Filters {
if strings.Contains(inputStr, filter) {
return
}
}
}
w.Fields["err"] = inputStr
logrus.WithFields(w.Fields).Error(w.Message)
return
}
================================================
FILE: lvm/lv.go
================================================
package lvm
import (
"fmt"
"math"
"path/filepath"
"strconv"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
func CreateLv(vgName, lvName string, size int) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"lvcreate", "-an", "-L", fmt.Sprintf("%d.1G", size),
"-n", lvName, vgName)
if err != nil {
return
}
return
}
func RemoveLv(vgName, lvName string) (err error) {
_, err = utils.ExecCombinedOutputLogged([]string{
"Failed to find",
}, "lvremove", "-y", fmt.Sprintf("%s/%s", vgName, lvName))
if err != nil {
return
}
return
}
func ActivateLv(vgName, lvName string) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"lvchange", "-ay", fmt.Sprintf("%s/%s", vgName, lvName))
if err != nil {
return
}
return
}
func DeactivateLv(vgName, lvName string) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"lvchange", "-an", fmt.Sprintf("%s/%s", vgName, lvName))
if err != nil {
return
}
return
}
func WriteLv(vgName, lvName, sourcePth string) (err error) {
dstPth := filepath.Join("/dev/mapper",
fmt.Sprintf("%s-%s", vgName, lvName))
_, err = utils.ExecCombinedOutputLogged(nil,
"qemu-img", "convert", "-f", "qcow2",
"-O", "raw", sourcePth, dstPth)
if err != nil {
return
}
return
}
func ExtendLv(vgName, lvName string, addSize int) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"lvextend", "-L", fmt.Sprintf("+%dG", addSize),
fmt.Sprintf("%s/%s", vgName, lvName))
if err != nil {
return
}
return
}
func GetSizeLv(vgName, lvName string) (size int, err error) {
output, err := utils.ExecCombinedOutput("",
"lvs", fmt.Sprintf("%s/%s", vgName, lvName),
"-o", "LV_SIZE", "--units", "g", "--noheadings")
if err != nil {
return
}
output = strings.Trim(strings.TrimSpace(strings.ToLower(output)), "g")
number, err := strconv.ParseFloat(output, 64)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "lvm: Failed to parse lvm volume size"),
}
return
}
size = int(math.Round(number))
return
}
func HasLocking(vgName string) (hasLock bool, err error) {
output, err := utils.ExecCombinedOutput("",
"vgs", vgName, "-o", "vg_lock_type", "--noheadings")
if err != nil {
return
}
lockType := strings.TrimSpace(output)
hasLock = lockType != "" && lockType != "none"
return
}
func IsLockspaceActive(vgName string) (isLocked bool, err error) {
output, err := utils.ExecCombinedOutput("",
"lvmlockctl", "-i")
if err != nil {
return
}
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, vgName) &&
strings.Contains(line, "sanlock") {
isLocked = true
return
}
}
return
}
func InitLock(vgName string) (err error) {
hasLock, err := HasLocking(vgName)
if err != nil {
return
}
if !hasLock {
return
}
isLocked, err := IsLockspaceActive(vgName)
if err != nil {
return
}
if isLocked {
return
}
_, err = utils.ExecCombinedOutputLogged(nil,
"vgchange", "--lock-start", vgName)
if err != nil {
return
}
return
}
================================================
FILE: lvm/vgs.go
================================================
package lvm
import (
"encoding/json"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
cachedNodePools []*pool.Pool
cachedNodePoolsTimestamp time.Time
)
type report struct {
Report []*vgReport `json:"report"`
}
type vgReport struct {
Vg []*vgDetails `json:"vg"`
}
type vgDetails struct {
VgName string `json:"vg_name"`
PvCount string `json:"pv_count"`
LvCount string `json:"lv_count"`
SnapCount string `json:"snap_count"`
VgAttr string `json:"vg_attr"`
VgSize string `json:"vg_size"`
VgFree string `json:"vg_free"`
}
func GetAvailablePools(db *database.Database, zoneId bson.ObjectID) (
availablePools []*pool.Pool, err error) {
if time.Since(cachedNodePoolsTimestamp) < 30*time.Second {
availablePools = cachedNodePools
return
}
availablePools = []*pool.Pool{}
vgNames := set.NewSet()
output, err := utils.ExecCombinedOutput("",
"vgs", "--reportformat", "json")
if err != nil {
return
}
reprt := &report{}
err = json.Unmarshal([]byte(output), reprt)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "deploy: Failed to unmarshal vgs report"),
}
return
}
if reprt.Report != nil {
for _, reportGroup := range reprt.Report {
if reportGroup.Vg != nil {
for _, reportVg := range reportGroup.Vg {
vgNames.Add(reportVg.VgName)
}
}
}
}
if vgNames.Len() > 0 {
pools, e := pool.GetAll(db, &bson.M{
"zone": zoneId,
})
if e != nil {
err = e
return
}
for _, pl := range pools {
if vgNames.Contains(pl.VgName) {
availablePools = append(availablePools, pl)
}
}
}
cachedNodePools = availablePools
cachedNodePoolsTimestamp = time.Now()
return
}
================================================
FILE: main.go
================================================
package main
import (
"flag"
"fmt"
"os"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/cmd"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/logger"
"github.com/pritunl/pritunl-cloud/requires"
)
const help = `
Usage: pritunl-cloud COMMAND
Commands:
version Show version
mongo Set MongoDB URI
set Set a setting
unset Unset a setting
start Start node
clear-logs Clear logs
reset-node-web Reset node web server settings
optimize Optimize system configuration
default-password Get default administrator password
reset-password Reset administrator password
disable-policies Disable all policies
disable-firewall Disable firewall on this node
start-instance Start instance by name
stop-instance Stop instance by name
mtu-check Check and show instance MTUs
backup Backup local data
`
func Init() {
logger.Init()
requires.Init(nil)
}
func InitLimited() {
logger.Init()
requires.Init([]string{"ahandlers", "uhandlers"})
}
func main() {
defer time.Sleep(500 * time.Millisecond)
command := ""
for _, arg := range os.Args[1:] {
if !strings.HasPrefix(arg, "-") {
command = arg
break
}
}
switch command {
case "start":
flag.Parse()
for _, arg := range flag.Args() {
switch arg {
case "--debug":
constants.Production = false
break
case "--debug-web":
constants.DebugWeb = true
break
case "--fast-exit":
constants.FastExit = true
break
}
}
Init()
err := cmd.Node()
if err != nil {
panic(err)
}
return
case "version":
fmt.Printf("pritunl-cloud v%s\n", constants.Version)
return
case "mongo":
flag.Parse()
logger.Init()
err := cmd.Mongo()
if err != nil {
panic(err)
}
return
case "optimize":
logger.Init()
err := cmd.Optimize()
if err != nil {
panic(err)
}
return
case "reset-node-web":
InitLimited()
err := cmd.ResetNodeWeb()
if err != nil {
panic(err)
}
return
case "default-password":
InitLimited()
err := cmd.DefaultPassword()
if err != nil {
panic(err)
}
return
case "reset-password":
InitLimited()
err := cmd.ResetPassword()
if err != nil {
panic(err)
}
return
case "disable-policies":
InitLimited()
err := cmd.DisablePolicies()
if err != nil {
panic(err)
}
return
case "disable-firewall":
InitLimited()
err := cmd.DisableFirewall()
if err != nil {
panic(err)
}
return
case "mtu-check":
InitLimited()
err := cmd.MtuCheck()
if err != nil {
panic(err)
}
return
case "set":
flag.Parse()
InitLimited()
err := cmd.SettingsSet()
if err != nil {
panic(err)
}
return
case "unset":
flag.Parse()
InitLimited()
err := cmd.SettingsUnset()
if err != nil {
panic(err)
}
return
case "clear-logs":
InitLimited()
err := cmd.ClearLogs()
if err != nil {
panic(err)
}
return
case "backup":
flag.Parse()
InitLimited()
err := cmd.Backup()
if err != nil {
panic(err)
}
return
case "imds-server":
err := cmd.ImdsServer()
if err != nil {
panic(err)
}
return
case "dhcp4-server":
err := cmd.Dhcp4Server()
if err != nil {
panic(err)
}
return
case "dhcp6-server":
err := cmd.Dhcp6Server()
if err != nil {
panic(err)
}
return
case "dhcp-client":
err := cmd.DhcpClient()
if err != nil {
panic(err)
}
return
case "ndp-server":
err := cmd.NdpServer()
if err != nil {
panic(err)
}
return
case "start-instance":
flag.Parse()
InitLimited()
err := cmd.StartInstance(flag.Args()[1])
if err != nil {
panic(err)
}
return
case "stop-instance":
flag.Parse()
InitLimited()
err := cmd.StopInstance(flag.Args()[1])
if err != nil {
panic(err)
}
return
default:
fmt.Printf(help)
}
}
================================================
FILE: middlewear/gzip.go
================================================
package middlewear
import (
"compress/gzip"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
type GzipWriter struct {
gzipWriter *gzip.Writer
httpWriter http.ResponseWriter
}
func (g *GzipWriter) Header() http.Header {
return g.httpWriter.Header()
}
func (g *GzipWriter) WriteHeader(statusCode int) {
g.httpWriter.WriteHeader(statusCode)
}
func (g *GzipWriter) Write(b []byte) (int, error) {
if g.gzipWriter != nil {
return g.gzipWriter.Write(b)
}
return g.httpWriter.Write(b)
}
func (g *GzipWriter) Close() {
if g.gzipWriter != nil {
g.gzipWriter.Close()
}
}
func NewGzipWriter(c *gin.Context) *GzipWriter {
if !strings.Contains(c.GetHeader("Accept-Encoding"), "gzip") {
return &GzipWriter{
httpWriter: c.Writer,
}
}
c.Writer.Header().Set("Content-Encoding", "gzip")
c.Writer.Header().Set("Vary", "Accept-Encoding")
gz, _ := gzip.NewWriterLevel(c.Writer, gzip.DefaultCompression)
return &GzipWriter{
gzipWriter: gz,
httpWriter: c.Writer,
}
}
================================================
FILE: middlewear/middlewear.go
================================================
package middlewear
import (
"fmt"
"net/http"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/auth"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/csrf"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/organization"
"github.com/pritunl/pritunl-cloud/session"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/validator"
"github.com/sirupsen/logrus"
)
const robots = `User-agent: *
Disallow: /
`
func Limiter(c *gin.Context) {
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1000000)
}
func Counter(c *gin.Context) {
node.Self.AddRequest()
}
func Database(c *gin.Context) {
db := database.GetDatabaseCtx(c.Request.Context())
c.Set("db", db)
c.Next()
db.Close()
}
func Headers(c *gin.Context) {
headers := c.Writer.Header()
headers.Add("Cache-Control", "no-store")
headers.Add("X-Frame-Options", "DENY")
headers.Add("X-XSS-Protection", "1; mode=block")
headers.Add("X-Content-Type-Options", "nosniff")
headers.Add("X-Robots-Tag", "noindex")
}
func SessionAdmin(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr, err := authorizer.AuthorizeAdmin(db, c.Writer, c.Request)
if err != nil {
switch err.(type) {
case *errortypes.AuthenticationError:
utils.AbortWithError(c, 401, err)
break
default:
utils.AbortWithError(c, 500, err)
}
return
}
if authr.IsValid() {
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if usr != nil {
active, err := auth.SyncUser(db, usr)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !active {
err = authr.Clear(db, c.Writer, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = session.RemoveAll(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
}
}
c.Set("authorizer", authr)
}
func SessionUser(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr, err := authorizer.AuthorizeUser(db, c.Writer, c.Request)
if err != nil {
switch err.(type) {
case *errortypes.AuthenticationError:
utils.AbortWithError(c, 401, err)
break
default:
utils.AbortWithError(c, 500, err)
}
return
}
if authr.IsValid() {
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if usr != nil {
active, err := auth.SyncUser(db, usr)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !active {
err = authr.Clear(db, c.Writer, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = session.RemoveAll(db, usr.Id)
if err != nil {
return
}
}
}
}
c.Set("authorizer", authr)
}
func AuthAdmin(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
utils.AbortWithStatus(c, 401)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if usr == nil {
utils.AbortWithStatus(c, 401)
return
}
_, _, errAudit, errData, err := validator.ValidateAdmin(
db, usr, authr.IsApi(), c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = authr.Clear(db, c.Writer, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "check"
err = audit.New(
db,
c.Request,
usr.Id,
audit.AdminAuthFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
utils.AbortWithStatus(c, 401)
return
}
}
func AuthUser(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
utils.AbortWithStatus(c, 401)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if usr == nil {
utils.AbortWithStatus(c, 401)
return
}
_, _, errAudit, errData, err := validator.ValidateUser(
db, usr, authr.IsApi(), c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = authr.Clear(db, c.Writer, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "check"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserAuthFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
utils.AbortWithStatus(c, 401)
return
}
}
func UserOrg(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
utils.AbortWithStatus(c, 401)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
orgIdStr := ""
if strings.ToLower(c.Request.Header.Get("Upgrade")) == "websocket" {
orgIdStr = c.Query("organization")
} else {
orgIdStr = c.GetHeader("Organization")
}
if orgIdStr == "" {
utils.AbortWithStatus(c, 401)
return
}
orgId, ok := utils.ParseObjectId(orgIdStr)
if orgId.IsZero() || !ok {
utils.AbortWithStatus(c, 400)
return
}
org, err := organization.Get(db, orgId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
match := usr.RolesMatch(org.Roles)
if !match {
utils.AbortWithStatus(c, 401)
return
}
c.Set("organization", org.Id)
}
func CsrfToken(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
utils.AbortWithStatus(c, 401)
return
}
if authr.IsApi() {
return
}
token := ""
if strings.ToLower(c.Request.Header.Get("Upgrade")) == "websocket" {
token = c.Query("csrf_token")
} else {
token = c.Request.Header.Get("Csrf-Token")
}
valid, err := csrf.ValidateToken(db, authr.SessionId(), token)
if err != nil {
switch err.(type) {
case *database.NotFoundError:
utils.AbortWithStatus(c, 401)
break
default:
utils.AbortWithError(c, 500, err)
}
return
}
if !valid {
utils.AbortWithStatus(c, 401)
return
}
}
func Recovery(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
logrus.WithFields(logrus.Fields{
"client": node.Self.GetRemoteAddr(c.Request),
"error": errors.New(fmt.Sprintf("%s", r)),
}).Error("middlewear: Handler panic")
utils.AbortWithStatus(c, 500)
return
}
}()
defer func() {
if c.Errors != nil && len(c.Errors) != 0 {
logrus.WithFields(logrus.Fields{
"client": node.Self.GetRemoteAddr(c.Request),
"error": c.Errors,
}).Error("middlewear: Handler error")
}
}()
c.Next()
}
func RobotsGet(c *gin.Context) {
c.String(200, robots)
}
func NotFound(c *gin.Context) {
utils.AbortWithStatus(c, 404)
}
================================================
FILE: mtu/mtu.go
================================================
package mtu
import (
"fmt"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/config"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/ip"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/vm"
)
type Check struct {
node *node.Node
mtuInternal int
mtuExternal int
mtuInstance int
mtuHost int
Instances []*instance.Instance
}
func (c *Check) host(db *database.Database) (err error) {
ifaces, e := ip.GetIfaces("")
if e != nil {
err = e
return
}
internalIfaces := set.NewSet()
externalIfaces := set.NewSet()
for _, iface := range c.node.InternalInterfaces {
internalIfaces.Add(iface)
}
for _, iface := range c.node.ExternalInterfaces {
externalIfaces.Add(iface)
}
for _, iface := range c.node.ExternalInterfaces6 {
externalIfaces.Add(iface)
}
for _, blck := range c.node.Blocks {
externalIfaces.Add(blck.Interface)
}
for _, blck := range c.node.Blocks6 {
externalIfaces.Add(blck.Interface)
}
fmt.Println("*******************************************")
fmt.Printf("host: %s\n", c.node.Name)
for _, iface := range ifaces {
mtu := 0
if iface.Ifname == settings.Hypervisor.HostNetworkName {
mtu = c.mtuHost
} else if iface.Ifname == settings.Hypervisor.NodePortNetworkName {
mtu = c.mtuHost
} else if internalIfaces.Contains(iface.Ifname) {
mtu = c.mtuHost
} else if externalIfaces.Contains(iface.Ifname) {
mtu = c.mtuExternal
} else if len(iface.Ifname) != 14 {
continue
} else if strings.HasPrefix(iface.Ifname, "b") {
mtu = c.mtuInternal
} else if strings.HasPrefix(iface.Ifname, "k") {
mtu = c.mtuInternal
} else if strings.HasPrefix(iface.Ifname, "r") {
mtu = c.mtuExternal
} else if strings.HasPrefix(iface.Ifname, "j") {
mtu = c.mtuInternal
} else if strings.HasPrefix(iface.Ifname, "k") {
mtu = c.mtuInternal
} else {
continue
}
if iface.Mtu != mtu {
fmt.Printf("◆◆◆ERROR◆◆◆\n%s: %d (%d)\n",
iface.Ifname, iface.Mtu, mtu)
} else {
fmt.Printf("%s: %d\n", iface.Ifname, iface.Mtu)
}
}
fmt.Println("*******************************************")
return
}
func (c *Check) instances(db *database.Database) (err error) {
insts, err := instance.GetAll(db, &bson.M{
"node": c.node.Id,
})
for _, inst := range insts {
if inst.State != vm.Running {
continue
}
namespace := inst.NetworkNamespace
if namespace == "" {
continue
}
ifaces, e := ip.GetIfaces(namespace)
if e != nil {
err = e
return
}
fmt.Println("*******************************************")
fmt.Printf("instance: %s\n", inst.Name)
for _, iface := range ifaces {
mtu := 0
if iface.Ifname == settings.Hypervisor.BridgeIfaceName {
mtu = c.mtuInstance
} else if iface.Ifname == "lo" {
continue
} else if strings.HasPrefix(iface.Ifname, "p") {
mtu = c.mtuInstance
} else if strings.HasPrefix(iface.Ifname, "e") {
mtu = c.mtuExternal
} else if strings.HasPrefix(iface.Ifname, "i") {
mtu = c.mtuInternal
} else if strings.HasPrefix(iface.Ifname, "x") {
mtu = c.mtuInternal
} else if strings.HasPrefix(iface.Ifname, "h") {
mtu = c.mtuHost
} else if strings.HasPrefix(iface.Ifname, "m") {
mtu = c.mtuHost
} else {
fmt.Println("◆◆◆UNKNOWN IFACE◆◆◆")
}
if iface.Mtu != mtu {
fmt.Printf("◆◆◆ERROR◆◆◆\n%s-%s: %d (%d)\n", namespace,
iface.Ifname, iface.Mtu, mtu)
} else {
fmt.Printf("%s-%s: %d\n", namespace,
iface.Ifname, iface.Mtu)
}
}
fmt.Println("*******************************************")
}
return
}
func (c *Check) Run() (err error) {
db := database.GetDatabase()
defer db.Close()
ndeId, err := bson.ObjectIDFromHex(config.Config.NodeId)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "backup: Failed to parse ObjectId"),
}
return
}
c.node, err = node.Get(db, ndeId)
if err != nil {
return
}
dc, err := datacenter.Get(db, c.node.Datacenter)
if err != nil {
return
}
c.mtuInternal -= dc.GetOverlayMtu()
c.mtuInstance -= dc.GetInstanceMtu()
err = c.host(db)
if err != nil {
return
}
err = c.instances(db)
if err != nil {
return
}
return
}
func NewCheck() (chk *Check) {
return &Check{}
}
================================================
FILE: netconf/address.go
================================================
package netconf
import (
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/interfaces"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
)
func (n *NetConf) Address(db *database.Database) (err error) {
vc, err := vpc.Get(db, n.VmAdapter.Vpc)
if err != nil {
return
}
n.VlanId = vc.VpcId
vcNet, err := vc.GetNetwork()
if err != nil {
return
}
cidr, _ := vcNet.Mask.Size()
addr, gatewayAddr, err := vc.GetIp(db, n.VmAdapter.Subnet, n.Virt.Id)
if err != nil {
return
}
n.InternalAddr = addr
n.InternalGatewayAddr = gatewayAddr
n.InternalGatewayAddrCidr = fmt.Sprintf(
"%s/%d", gatewayAddr.String(), cidr)
n.InternalAddr6 = vc.GetIp6(n.Virt.Id)
n.InternalGatewayAddr6 = vc.GetGatewayIp6(n.Virt.Id)
n.ExternalMacAddr = vm.GetMacAddrExternal(n.Virt.Id, vc.Id)
n.InternalMacAddr = vm.GetMacAddrInternal(n.Virt.Id, vc.Id)
n.HostMacAddr = vm.GetMacAddrHost(n.Virt.Id, vc.Id)
n.NodePortMacAddr = vm.GetMacAddrNodePort(n.Virt.Id, vc.Id)
if n.NetworkMode == node.Dhcp {
n.PhysicalExternalIface = interfaces.GetExternal(
n.SystemExternalIface)
} else if n.NetworkMode == node.Static {
blck, staticAddr, externalIface, e := node.Self.GetStaticAddr(
db, n.Virt.Id)
if e != nil {
err = e
return
}
n.PhysicalExternalIface = externalIface
staticGateway := blck.GetGateway()
staticMask := blck.GetMask()
if staticGateway == nil || staticMask == nil {
err = &errortypes.ParseError{
errors.New("qemu: Invalid block gateway cidr"),
}
return
}
staticSize, _ := staticMask.Size()
staticCidr := fmt.Sprintf(
"%s/%d", staticAddr.String(), staticSize)
n.ExternalVlan = blck.Vlan
if n.ExternalVlan != 0 {
n.SpaceExternalIfaceMod = n.SpaceExternalIface + "x"
}
n.ExternalAddrCidr = staticCidr
n.ExternalGatewayAddr = staticGateway
} else if n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud {
n.PhysicalExternalIface = interfaces.GetExternal(
n.SystemExternalIface)
}
if n.NetworkMode6 == node.Static {
blck, staticAddr, prefix, iface, e := node.Self.GetStaticAddr6(
db, n.Virt.Id, vc.VpcId, n.PhysicalExternalIface)
if e != nil {
err = e
return
}
if n.PhysicalExternalIface != "" && n.PhysicalExternalIface != iface {
err = &errortypes.ParseError{
errors.Newf(
"netconf: Unsupported mismatched external "+
"IPv4 and IPv6 iface %s - %s",
n.PhysicalExternalIface, iface,
),
}
return
}
n.PhysicalExternalIface = iface
staticCidr6 := fmt.Sprintf("%s/%d", staticAddr.String(), prefix)
gateway6 := blck.GetGateway6()
n.ExternalVlan6 = blck.Vlan
if n.ExternalVlan6 != 0 {
if n.ExternalVlan == n.ExternalVlan6 {
n.SpaceExternalIfaceMod6 = n.SpaceExternalIfaceMod
} else {
n.SpaceExternalIfaceMod6 = n.SpaceExternalIface + "y"
}
}
n.ExternalAddrCidr6 = staticCidr6
n.ExternalGatewayAddr6 = gateway6
}
if n.HostNetwork {
blck, staticAddr, e := node.Self.GetStaticHostAddr(db, n.Virt.Id)
if e != nil {
err = e
return
}
n.HostAddr = staticAddr
hostStaticGateway := blck.GetGateway()
hostStaticMask := blck.GetMask()
if hostStaticGateway == nil || hostStaticMask == nil {
err = &errortypes.ParseError{
errors.New("qemu: Invalid block gateway cidr"),
}
return
}
hostStaticSize, _ := hostStaticMask.Size()
hostStaticCidr := fmt.Sprintf(
"%s/%d", staticAddr.String(), hostStaticSize)
n.HostAddrCidr = hostStaticCidr
n.HostGatewayAddr = hostStaticGateway
}
if n.NodePortNetwork {
blck, staticAddr, e := node.Self.GetStaticNodePortAddr(db, n.Virt.Id)
if e != nil {
err = e
return
}
n.NodePortAddr = staticAddr
nodePortStaticMask := blck.GetMask()
if nodePortStaticMask == nil {
err = &errortypes.ParseError{
errors.New("qemu: Invalid block gateway cidr"),
}
return
}
nodePortStaticSize, _ := nodePortStaticMask.Size()
nodePortStaticCidr := fmt.Sprintf(
"%s/%d", staticAddr.String(), nodePortStaticSize)
n.NodePortAddrCidr = nodePortStaticCidr
}
return
}
================================================
FILE: netconf/base.go
================================================
package netconf
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) Base(db *database.Database) (err error) {
if n.PhysicalExternalIface != "" {
n.PhysicalExternalIfaceBridge, err = utils.IsInterfaceBridge(
n.PhysicalExternalIface)
if err != nil {
return
}
}
n.PhysicalInternalIfaceBridge, err = utils.IsInterfaceBridge(
n.PhysicalInternalIface)
if err != nil {
return
}
return
}
================================================
FILE: netconf/bridge.go
================================================
package netconf
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/iptables"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) bridgeNet(db *database.Database) (err error) {
err = iproute.BridgeAdd(n.Namespace, n.SpaceBridgeIface)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
"net.ipv4.conf.br0.arp_accept=0",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
"net.ipv4.conf.br0.arp_ignore=2",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
"net.ipv4.conf.br0.arp_filter=1",
)
if err != nil {
return
}
return
}
func (n *NetConf) bridgeMaster(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link", "set",
n.BridgeInternalIface, "master", n.SpaceBridgeIface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link", "set",
n.VirtIface, "master", n.SpaceBridgeIface,
)
if err != nil {
return
}
return
}
func (n *NetConf) bridgeRoute(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.InternalGatewayAddrCidr,
"dev", n.SpaceBridgeIface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "-6", "addr",
"add", n.InternalGatewayAddr6.String()+"/64",
"dev", n.SpaceBridgeIface,
)
if err != nil {
return
}
return
}
func (n *NetConf) bridgeIptables(db *database.Database) (err error) {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "INPUT",
"-p", "ARP",
"-i", "!", n.VirtIface,
"--arp-ip-dst", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "OUTPUT",
"-p", "ARP",
"-o", "!", n.VirtIface,
"--arp-ip-dst", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "FORWARD",
"-p", "ARP",
"-o", "!", n.VirtIface,
"--arp-ip-dst", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "INPUT",
"-p", "ARP",
"-i", "!", n.VirtIface,
"--arp-ip-src", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "OUTPUT",
"-p", "ARP",
"-o", "!", n.VirtIface,
"--arp-ip-src", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ebtables",
"-A", "FORWARD",
"-p", "ARP",
"-o", "!", n.VirtIface,
"--arp-ip-src", n.InternalGatewayAddr.String(),
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
return
}
func (n *NetConf) bridgeUp(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceBridgeIface, "up",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"bridge", "link",
"set", "dev", n.VirtIface, "hairpin", "on",
)
if err != nil {
return
}
return
}
func (n *NetConf) Bridge(db *database.Database) (err error) {
err = n.bridgeNet(db)
if err != nil {
return
}
err = n.bridgeMaster(db)
if err != nil {
return
}
err = n.bridgeRoute(db)
if err != nil {
return
}
err = n.bridgeIptables(db)
if err != nil {
return
}
err = n.bridgeUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/clear.go
================================================
package netconf
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/interfaces"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/tools/commander"
)
func (n *NetConf) Clear(db *database.Database) (err error) {
lockId := lock.Lock("clear")
defer lock.Unlock("clear", lockId)
clearIface("", n.SystemExternalIface) // bridged
clearIface("", n.SystemInternalIface) // bridged
clearIface("", n.SystemHostIface) // bridged
clearIface("", n.SystemNodePortIface) // bridged
clearIface("", n.SpaceExternalIface)
clearIface("", n.SpaceExternalIfaceMod)
clearIface("", n.SpaceExternalIfaceMod6)
clearIface("", n.SpaceInternalIface)
clearIface("", n.SpaceHostIface)
clearIface("", n.SpaceNodePortIface)
clearIface(n.Namespace, n.SpaceBridgeIface)
clearIface(n.Namespace, n.SpaceImdsIface)
interfaces.RemoveVirtIface(n.SystemExternalIface)
interfaces.RemoveVirtIface(n.SystemInternalIface)
interfaces.RemoveVirtIface(n.SystemNodePortIface)
return
}
func (n *NetConf) ClearAll(db *database.Database) (err error) {
if len(n.Virt.NetworkAdapters) == 0 {
err = &errortypes.NotFoundError{
errors.New("qemu: Missing network interfaces"),
}
return
}
err = n.Clear(db)
if err != nil {
return
}
store.RemAddress(n.Virt.Id)
store.RemRoutes(n.Virt.Id)
store.RemArp(n.Virt.Id)
return
}
func clearIface(namespace, iface string) {
if iface == "" {
return
}
if namespace != "" {
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", namespace,
"ip", "link", "set", iface, "nomaster",
},
PipeOut: true,
PipeErr: true,
})
time.Sleep(200 * time.Millisecond)
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", namespace,
"ip", "link", "set", iface, "down",
},
PipeOut: true,
PipeErr: true,
})
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", namespace,
"ip", "link", "del", iface,
},
PipeOut: true,
PipeErr: true,
})
} else {
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"link", "set", iface, "nomaster",
},
PipeOut: true,
PipeErr: true,
})
time.Sleep(200 * time.Millisecond)
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"link", "set", iface, "down",
},
PipeOut: true,
PipeErr: true,
})
commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"link", "del", iface,
},
PipeOut: true,
PipeErr: true,
})
}
}
================================================
FILE: netconf/external.go
================================================
package netconf
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) externalNet(db *database.Database) (err error) {
if (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {
if n.PhysicalExternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SystemExternalIface,
"type", "veth",
"peer", "name", n.SpaceExternalIface,
"addr", n.ExternalMacAddr,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"tc", "qdisc", "replace", "dev", n.SystemExternalIface,
"root", "fq_codel",
)
if err != nil {
return err
}
} else {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SpaceExternalIface,
"addr", n.ExternalMacAddr,
"link", n.PhysicalExternalIface,
"type", "macvlan",
"mode", "bridge",
)
if err != nil {
return
}
}
}
return
}
func (n *NetConf) externalMtu(db *database.Database) (err error) {
if (n.PhysicalExternalIfaceBridge &&
n.SystemExternalIfaceMtu != "") &&
((n.NetworkMode != node.Disabled &&
n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud)) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemExternalIface,
"mtu", n.SystemExternalIfaceMtu,
)
if err != nil {
return
}
}
if n.SpaceExternalIfaceMtu != "" &&
((n.NetworkMode != node.Disabled &&
n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud)) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SpaceExternalIface,
"mtu", n.SpaceExternalIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) externalUp(db *database.Database) (err error) {
if n.PhysicalExternalIfaceBridge &&
((n.NetworkMode != node.Disabled &&
n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud)) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemExternalIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) externalSysctl(db *database.Database) (err error) {
if n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud {
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.accept_ra=2",
strings.ReplaceAll(n.PhysicalExternalIface, ".", "/")),
)
if err != nil {
return
}
if n.NetworkMode6 != node.Slaac && n.NetworkMode6 != node.DhcpSlaac {
_, err = utils.ExecCombinedOutputLogged(
nil, "sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.addr_gen_mode=1",
strings.ReplaceAll(n.PhysicalExternalIface, ".", "/")),
)
if err != nil {
return
}
}
}
return
}
func (n *NetConf) externalMaster(db *database.Database) (err error) {
if n.PhysicalExternalIfaceBridge &&
((n.NetworkMode != node.Disabled &&
n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud)) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", n.SystemExternalIface,
"master", n.PhysicalExternalIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) externalSpace(db *database.Database) (err error) {
if (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceExternalIface,
"netns", n.Namespace,
)
if err != nil {
return
}
if n.PhysicalExternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"tc", "qdisc", "replace", "dev", n.SpaceExternalIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
}
return
}
func (n *NetConf) externalSpaceMod(db *database.Database) (err error) {
if n.SpaceExternalIfaceMod != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"add", "link", n.SpaceExternalIface,
"name", n.SpaceExternalIfaceMod,
"type", "vlan",
"id", strconv.Itoa(n.ExternalVlan),
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceExternalIfaceMod,
"mtu", n.SpaceExternalIfaceMtu,
)
if err != nil {
return
}
}
if n.SpaceExternalIfaceMod6 != "" &&
n.SpaceExternalIfaceMod6 != n.SpaceExternalIfaceMod {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"add", "link", n.SpaceExternalIface,
"name", n.SpaceExternalIfaceMod6,
"type", "vlan",
"id", strconv.Itoa(n.ExternalVlan6),
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceExternalIfaceMod6,
"mtu", n.SpaceExternalIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) externalSpaceSysctl(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv6.conf.all.accept_ra=0",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv6.conf.default.accept_ra=0",
)
if err != nil {
return
}
if n.NetworkMode6 == node.Static {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.autoconf=0",
n.SpaceExternalIface),
)
if err != nil {
return
}
if n.SpaceExternalIfaceMod6 != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.autoconf=0",
n.SpaceExternalIfaceMod6),
)
if err != nil {
return
}
}
}
if n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud {
if n.SpaceExternalIfaceMod6 == "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.accept_ra=2",
n.SpaceExternalIface),
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.accept_ra=2",
n.SpaceExternalIfaceMod6),
)
if err != nil {
return
}
}
}
if (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) &&
(n.NetworkMode6 == node.Disabled || n.NetworkMode6 == node.Cloud) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6=1",
n.SpaceExternalIface),
)
if err != nil {
return
}
}
if n.SpaceExternalIfaceMod != n.SpaceExternalIfaceMod6 &&
n.SpaceExternalIfaceMod != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6=1",
n.SpaceExternalIfaceMod),
)
if err != nil {
return
}
}
return
}
func (n *NetConf) externalSpaceUp(db *database.Database) (err error) {
if (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceExternalIface, "up",
)
if err != nil {
return
}
}
if n.SpaceExternalIfaceMod != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceExternalIfaceMod, "up",
)
if err != nil {
return
}
}
if n.SpaceExternalIfaceMod6 != "" &&
n.SpaceExternalIfaceMod6 != n.SpaceExternalIfaceMod {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceExternalIfaceMod6, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) External(db *database.Database) (err error) {
delay := time.Duration(settings.Hypervisor.ActionRate) * time.Second
lockId := lock.Lock("external")
defer lock.DelayUnlock("external", lockId, delay)
err = n.externalNet(db)
if err != nil {
return
}
err = n.externalMtu(db)
if err != nil {
return
}
err = n.externalUp(db)
if err != nil {
return
}
err = n.externalSysctl(db)
if err != nil {
return
}
err = n.externalMaster(db)
if err != nil {
return
}
err = n.externalSpace(db)
if err != nil {
return
}
err = n.externalSpaceMod(db)
if err != nil {
return
}
err = n.externalSpaceSysctl(db)
if err != nil {
return
}
err = n.externalSpaceUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/host.go
================================================
package netconf
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) hostNet(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SystemHostIface,
"type", "veth",
"peer", "name", n.SpaceHostIface,
"addr", n.HostMacAddr,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"tc", "qdisc", "replace", "dev", n.SystemHostIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
return
}
func (n *NetConf) hostMtu(db *database.Database) (err error) {
if n.HostNetwork && n.SystemHostIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemHostIface,
"mtu", n.SystemHostIfaceMtu,
)
if err != nil {
return
}
}
if n.HostNetwork && n.SpaceHostIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SpaceHostIface,
"mtu", n.SpaceHostIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) hostUp(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemHostIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) hostMaster(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", n.SystemHostIface,
"master", n.PhysicalHostIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) hostSpace(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceHostIface,
"netns", n.Namespace,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"tc", "qdisc", "replace", "dev", n.SpaceHostIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
return
}
func (n *NetConf) hostSpaceUp(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceHostIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) Host(db *database.Database) (err error) {
delay := time.Duration(settings.Hypervisor.ActionRate) * time.Second
lockId := lock.Lock("host")
defer lock.DelayUnlock("host", lockId, delay)
err = n.hostNet(db)
if err != nil {
return
}
err = n.hostMtu(db)
if err != nil {
return
}
err = n.hostUp(db)
if err != nil {
return
}
err = n.hostMaster(db)
if err != nil {
return
}
err = n.hostSpace(db)
if err != nil {
return
}
err = n.hostSpaceUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/iface.go
================================================
package netconf
import (
"strconv"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/interfaces"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/vm"
)
func (n *NetConf) Iface1(db *database.Database) (err error) {
n.NetworkMode = node.Self.NetworkMode
if n.NetworkMode == "" {
n.NetworkMode = node.Dhcp
}
n.NetworkMode6 = node.Self.NetworkMode6
if n.NetworkMode6 == "" {
n.NetworkMode6 = node.Dhcp
}
if n.NetworkMode == node.Internal || n.Virt.NoPublicAddress {
n.NetworkMode = node.Disabled
}
if n.Virt.NoPublicAddress6 {
n.NetworkMode6 = node.Disabled
}
if !node.Self.NoHostNetwork && !n.Virt.NoHostAddress {
n.HostNetwork = true
if node.Self.HostNat {
n.HostNat = true
}
blck, e := block.GetNodeBlock(node.Self.Id)
if e != nil {
err = e
return
}
hostNetwork, e := blck.GetNetwork()
if e != nil {
err = e
return
}
n.HostSubnet = hostNetwork.String()
}
if !node.Self.NoNodePortNetwork {
n.NodePortNetwork = true
blck, e := block.GetNodePortBlock(node.Self.Id)
if e != nil {
err = e
return
}
nodePortNetwork, e := blck.GetNetwork()
if e != nil {
err = e
return
}
n.NodePortSubnet = nodePortNetwork.String()
}
n.CloudSubnets = set.NewSet()
if node.Self.CloudSubnets != nil {
for _, subnet := range node.Self.CloudSubnets {
n.CloudSubnets.Add(subnet)
}
}
n.Namespace = vm.GetNamespace(n.Virt.Id, 0)
if n.Virt.NetworkAdapters == nil || len(n.Virt.NetworkAdapters) < 1 {
err = &errortypes.ParseError{
errors.New("netconf: Missing virt network adapter"),
}
return
}
n.VmAdapter = n.Virt.NetworkAdapters[0]
n.VirtIface = vm.GetIface(n.Virt.Id, 0)
n.SystemExternalIface = vm.GetIfaceNodeExternal(n.Virt.Id, 0)
n.SystemInternalIface = vm.GetIfaceNodeInternal(n.Virt.Id, 0)
n.SystemHostIface = vm.GetIfaceHost(n.Virt.Id, 0)
n.SystemNodePortIface = vm.GetIfaceNodePort(n.Virt.Id, 0)
n.SpaceExternalIface = vm.GetIfaceExternal(n.Virt.Id, 0)
n.SpaceInternalIface = vm.GetIfaceInternal(n.Virt.Id, 0)
n.SpaceHostIface = vm.GetIfaceHost(n.Virt.Id, 1)
n.SpaceNodePortIface = vm.GetIfaceNodePort(n.Virt.Id, 1)
n.SpaceCloudIface = vm.GetIfaceCloud(n.Virt.Id, 0)
n.SpaceCloudVirtIface = vm.GetIfaceCloudVirt(n.Virt.Id, 0)
n.SpaceBridgeIface = settings.Hypervisor.BridgeIfaceName
n.SpaceImdsIface = settings.Hypervisor.ImdsIfaceName
return
}
func (n *NetConf) Iface2(db *database.Database, clean bool) (err error) {
dc, err := datacenter.Get(db, node.Self.Datacenter)
if err != nil {
return
}
n.Vxlan = dc.Vxlan()
n.PhysicalHostIface = settings.Hypervisor.HostNetworkName
n.PhysicalNodePortIface = settings.Hypervisor.NodePortNetworkName
n.BridgeInternalIface = vm.GetIfaceVlan(n.Virt.Id, 0)
n.PhysicalInternalIface = interfaces.GetInternal(
n.SystemInternalIface, n.Vxlan)
mtuSizeExternal := dc.GetBaseExternalMtu()
mtuSizeInternal := dc.GetBaseInternalMtu()
mtuSizeOverlay := dc.GetOverlayMtu()
mtuSizeInstance := dc.GetInstanceMtu()
n.SpaceExternalIfaceMtu = strconv.Itoa(mtuSizeExternal)
n.SystemExternalIfaceMtu = strconv.Itoa(mtuSizeExternal)
n.SpaceHostIfaceMtu = strconv.Itoa(mtuSizeInternal)
n.SpaceNodePortIfaceMtu = strconv.Itoa(mtuSizeInternal)
n.SystemHostIfaceMtu = strconv.Itoa(mtuSizeInternal)
n.SystemNodePortIfaceMtu = strconv.Itoa(mtuSizeInternal)
n.ImdsIfaceMtu = strconv.Itoa(mtuSizeInternal)
n.SpaceInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)
n.BridgeInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)
n.SystemInternalIfaceMtu = strconv.Itoa(mtuSizeOverlay)
n.VirtIfaceMtu = strconv.Itoa(mtuSizeInstance)
return
}
================================================
FILE: netconf/imds.go
================================================
package netconf
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/imds"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) imdsNet(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"add", n.SpaceImdsIface,
"type", "dummy",
//"addr", n.ImdsMacAddr,
)
if err != nil {
return
}
return
}
func (n *NetConf) imdsMtu(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceImdsIface,
"mtu", n.ImdsIfaceMtu,
)
if err != nil {
return
}
return
}
func (n *NetConf) imdsAddr(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", settings.Hypervisor.ImdsAddress,
"dev", n.SpaceImdsIface,
)
if err != nil {
return
}
return
}
func (n *NetConf) imdsUp(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceImdsIface, "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) imdsStart(db *database.Database) (err error) {
err = imds.Start(db, n.Virt)
if err != nil {
return
}
return
}
func (n *NetConf) Imds(db *database.Database) (err error) {
err = n.imdsNet(db)
if err != nil {
return
}
err = n.imdsMtu(db)
if err != nil {
return
}
err = n.imdsAddr(db)
if err != nil {
return
}
err = n.imdsUp(db)
if err != nil {
return
}
err = n.imdsStart(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/internal.go
================================================
package netconf
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) internalNet(db *database.Database) (err error) {
if n.PhysicalInternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SystemInternalIface,
"type", "veth",
"peer", "name", n.SpaceInternalIface,
"addr", n.InternalMacAddr,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"tc", "qdisc", "replace", "dev", n.SystemInternalIface,
"root", "fq_codel",
)
if err != nil {
return err
}
} else {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SpaceInternalIface,
"addr", n.InternalMacAddr,
"link", n.PhysicalInternalIface,
"type", "macvlan",
"mode", "bridge",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) internalMtu(db *database.Database) (err error) {
if n.SystemInternalIfaceMtu != "" && n.PhysicalInternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemInternalIface,
"mtu", n.SystemInternalIfaceMtu,
)
if err != nil {
return
}
}
if n.SpaceInternalIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SpaceInternalIface,
"mtu", n.SpaceInternalIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) internalUp(db *database.Database) (err error) {
if n.PhysicalInternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemInternalIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) internalMaster(db *database.Database) (err error) {
if n.PhysicalInternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", n.SystemInternalIface,
"master", n.PhysicalInternalIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) internalSpace(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceInternalIface,
"netns", n.Namespace,
)
if err != nil {
return
}
if n.PhysicalInternalIfaceBridge {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"tc", "qdisc", "replace", "dev", n.SpaceInternalIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
return
}
func (n *NetConf) internalSpaceUp(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceInternalIface, "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) Internal(db *database.Database) (err error) {
delay := time.Duration(settings.Hypervisor.ActionRate) * time.Second
lockId := lock.Lock("internal")
defer lock.DelayUnlock("internal", lockId, delay)
err = n.internalNet(db)
if err != nil {
return
}
err = n.internalMtu(db)
if err != nil {
return
}
err = n.internalUp(db)
if err != nil {
return
}
err = n.internalMaster(db)
if err != nil {
return
}
err = n.internalSpace(db)
if err != nil {
return
}
err = n.internalSpaceUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/ip.go
================================================
package netconf
import (
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/dhcpc"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/imds"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/iptables"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
func (n *NetConf) ipExternal(db *database.Database) (err error) {
if n.NetworkMode == node.Dhcp || n.NetworkMode6 == node.Dhcp ||
n.NetworkMode6 == node.DhcpSlaac {
err = dhcpc.Start(
db,
n.Virt,
n.SpaceExternalIface,
n.SpaceExternalIface,
n.NetworkMode == node.Dhcp,
n.NetworkMode6 == node.Dhcp || n.NetworkMode6 == node.DhcpSlaac,
)
if err != nil {
return
}
var imdsErr error
ip4 := false
ip6 := false
ipTimeout := settings.Hypervisor.IpTimeout * 4
for i := 0; i < ipTimeout; i++ {
stat, e := imds.State(db, n.Virt.Id, n.Virt.ImdsHostSecret)
if e != nil {
imdsErr = e
time.Sleep(250 * time.Millisecond)
continue
}
if stat == nil {
time.Sleep(250 * time.Millisecond)
continue
}
if stat.DhcpIp != nil && stat.DhcpGateway != nil {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists", "already assigned"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", stat.DhcpIp.String(),
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "route",
"add", "default",
"via", stat.DhcpGateway.String(),
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
ip4 = true
}
if stat.DhcpIp6 != nil {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists", "already assigned"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", stat.DhcpIp6.String(),
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
ip6 = true
}
if (n.NetworkMode != node.Dhcp || ip4) &&
((n.NetworkMode6 != node.Dhcp &&
n.NetworkMode6 != node.DhcpSlaac) || ip6) {
break
}
time.Sleep(250 * time.Millisecond)
}
if !ip4 && n.NetworkMode == node.Dhcp {
if imdsErr != nil {
logrus.WithFields(logrus.Fields{
"instance": n.Virt.Id.Hex(),
"dhcp4": ip4,
"dhcp6": ip6,
"error": imdsErr,
}).Error("netconf: DHCP IPv4 timeout")
} else {
logrus.WithFields(logrus.Fields{
"instance": n.Virt.Id.Hex(),
}).Error("netconf: DHCP IPv4 timeout")
}
}
if !ip6 && (n.NetworkMode6 == node.Dhcp ||
n.NetworkMode6 == node.DhcpSlaac) {
if imdsErr != nil {
logrus.WithFields(logrus.Fields{
"instance": n.Virt.Id.Hex(),
"dhcp4": ip4,
"dhcp6": ip6,
"error": imdsErr,
}).Error("netconf: DHCP IPv6 timeout")
} else {
logrus.WithFields(logrus.Fields{
"instance": n.Virt.Id.Hex(),
"dhcp4": ip4,
"dhcp6": ip6,
}).Error("netconf: DHCP IPv6 timeout")
}
}
}
if n.NetworkMode == node.Static {
if n.SpaceExternalIfaceMod != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.ExternalAddrCidr,
"dev", n.SpaceExternalIfaceMod,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "route",
"add", "default",
"via", n.ExternalGatewayAddr.String(),
"dev", n.SpaceExternalIfaceMod,
)
if err != nil {
return
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.ExternalAddrCidr,
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "route",
"add", "default",
"via", n.ExternalGatewayAddr.String(),
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
}
}
if n.NetworkMode6 == node.Static {
if n.SpaceExternalIfaceMod6 != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.ExternalAddrCidr6,
"dev", n.SpaceExternalIfaceMod6,
)
if err != nil {
return
}
if n.ExternalGatewayAddr6 != nil {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "-6", "route",
"add", "default",
"via", n.ExternalGatewayAddr6.String(),
"dev", n.SpaceExternalIfaceMod6,
)
if err != nil {
return
}
}
} else {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.ExternalAddrCidr6,
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
if n.ExternalGatewayAddr6 != nil {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "-6", "route",
"add", "default",
"via", n.ExternalGatewayAddr6.String(),
"dev", n.SpaceExternalIface,
)
if err != nil {
return
}
}
}
}
return
}
func (n *NetConf) ipHost(db *database.Database) (err error) {
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.HostAddrCidr,
"dev", n.SpaceHostIface,
)
if err != nil {
return
}
if n.NetworkMode == node.Disabled {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "route",
"add", "default",
"via", n.HostGatewayAddr.String(),
)
if err != nil {
return
}
}
}
return
}
func (n *NetConf) ipNodePort(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.NodePortAddrCidr,
"dev", n.SpaceNodePortIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) ipDetect(db *database.Database) (err error) {
time.Sleep(250 * time.Millisecond)
ipTimeout := settings.Hypervisor.IpTimeout * 4
ipTimeout6 := settings.Hypervisor.IpTimeout6 * 4
pubAddr := ""
pubAddr6 := ""
if n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud {
for i := 0; i < ipTimeout; i++ {
address, address6, e := iproute.AddressGetIfaceMod(
n.Namespace, n.SpaceExternalIface)
if e != nil {
err = e
return
}
if n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud {
if address != nil {
pubAddr = address.Local
}
if address != nil && address6 != nil {
if address6 != nil {
pubAddr6 = address6.Local
}
break
}
} else if address != nil {
pubAddr = address.Local
break
}
time.Sleep(250 * time.Millisecond)
}
if pubAddr == "" {
err = &errortypes.NetworkError{
errors.New("qemu: Instance missing IPv4 address"),
}
return
}
} else if n.NetworkMode6 != node.Disabled &&
n.NetworkMode6 != node.Cloud {
for i := 0; i < ipTimeout6; i++ {
_, address6, e := iproute.AddressGetIfaceMod(
n.Namespace, n.SpaceExternalIface)
if e != nil {
err = e
return
}
if address6 != nil {
pubAddr6 = address6.Local
break
}
time.Sleep(250 * time.Millisecond)
}
if pubAddr6 == "" {
err = &errortypes.NetworkError{
errors.New("qemu: Instance missing IPv6 address"),
}
return
}
}
n.PublicAddress = pubAddr
if n.ExternalAddrCidr6 != "" {
n.PublicAddress6 = n.ExternalAddrCidr6
} else {
n.PublicAddress6 = pubAddr6
}
return
}
func (n *NetConf) ipHostIptables(db *database.Database) (err error) {
if n.HostNetwork {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables", "-t", "nat",
"-A", "POSTROUTING",
"-s", n.InternalAddr.String()+"/32",
"-d", n.InternalAddr.String()+"/32",
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "SNAT",
"--to", n.HostAddr.String(),
)
iptables.Unlock()
if err != nil {
return
}
if n.HostNat {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables", "-t", "nat",
"-A", "POSTROUTING",
"-s", n.InternalAddr.String()+"/32",
"-o", n.SpaceHostIface,
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "MASQUERADE",
)
iptables.Unlock()
if err != nil {
return
}
} else {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables", "-t", "nat",
"-A", "POSTROUTING",
"-s", n.InternalAddr.String()+"/32",
"-d", n.HostSubnet,
"-o", n.SpaceHostIface,
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "MASQUERADE",
)
iptables.Unlock()
if err != nil {
return
}
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables", "-t", "nat",
"-A", "PREROUTING",
"-d", n.HostAddr.String()+"/32",
"-m", "comment",
"--comment", "pritunl_cloud_host_nat",
"-j", "DNAT",
"--to-destination", n.InternalAddr.String(),
)
iptables.Unlock()
if err != nil {
return
}
}
return
}
func (n *NetConf) ipDatabase(db *database.Database) (err error) {
store.RemAddress(n.Virt.Id)
store.RemRoutes(n.Virt.Id)
store.RemArp(n.Virt.Id)
hostIps := []string{}
if n.HostAddr != nil {
hostIps = append(hostIps, n.HostAddr.String())
}
nodePortIps := []string{}
if n.NodePortAddr != nil {
nodePortIps = append(nodePortIps, n.NodePortAddr.String())
}
coll := db.Instances()
err = coll.UpdateId(n.Virt.Id, &bson.M{
"$set": &bson.M{
"private_ips": []string{n.InternalAddr.String()},
"private_ips6": []string{n.InternalAddr6.String()},
"gateway_ips": []string{n.InternalGatewayAddrCidr},
"gateway_ips6": []string{
n.InternalGatewayAddr6.String() + "/64"},
"network_namespace": n.Namespace,
"host_ips": hostIps,
"node_port_ips": nodePortIps,
},
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if !n.Virt.Deployment.IsZero() {
coll = db.Deployments()
hostIps := []string{}
if n.HostAddr != nil {
hostIps = append(hostIps, n.HostAddr.String())
}
privateIps := []string{}
if n.InternalAddr != nil {
privateIps = append(privateIps, n.InternalAddr.String())
}
privateIps6 := []string{}
if n.InternalAddr6 != nil {
privateIps6 = append(privateIps6, n.InternalAddr6.String())
}
err = coll.UpdateId(n.Virt.Deployment, &bson.M{
"$set": &bson.M{
"instance_data.host_ips": hostIps,
"instance_data.private_ips": privateIps,
"instance_data.private_ips6": privateIps6,
},
})
if err != nil {
err = database.ParseError(err)
return
}
}
return
}
func (n *NetConf) ipInit6(db *database.Database) (err error) {
if n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud &&
n.PublicAddress6 != "" && !settings.Hypervisor.NoIpv6PingInit {
for i := 0; i < 3; i++ {
time.Sleep(200 * time.Millisecond)
resp, e := commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "dig",
"@" + settings.Hypervisor.DnsServerPrimary6,
"app6.pritunl.com",
"AAAA",
},
Timeout: 5 * time.Second,
PipeOut: true,
PipeErr: true,
})
if e != nil {
output := ""
if resp != nil {
output = string(resp.Output)
}
logrus.WithFields(logrus.Fields{
"instance_id": n.Virt.Id.Hex(),
"namespace": n.Namespace,
"address6": n.PublicAddress6,
"output": output,
}).Warn("netconf: IPv6 network DNS lookup test failed")
continue
}
resp, e = commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "ping6",
"-c", "3", "-i", "0.5", "-w", "6",
settings.Hypervisor.Ipv6PingHost,
},
Timeout: 6 * time.Second,
PipeOut: true,
PipeErr: true,
})
if e != nil {
output := ""
if resp != nil {
output = string(resp.Output)
}
logrus.WithFields(logrus.Fields{
"instance_id": n.Virt.Id.Hex(),
"namespace": n.Namespace,
"address6": n.PublicAddress6,
"output": output,
}).Warn("netconf: IPv6 network DNS lookup test failed")
continue
}
break
}
}
return
}
func (n *NetConf) ipArp(db *database.Database) (err error) {
if n.NetworkMode == node.Static {
addr := strings.Split(n.ExternalAddrCidr, "/")[0]
iface := n.SpaceExternalIfaceMod
if iface == "" {
iface = n.SpaceExternalIface
}
_, _ = commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "arping",
"-U", "-I", iface, "-c", "3", addr,
},
Timeout: 6 * time.Second,
PipeOut: true,
PipeErr: true,
})
_, _ = commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "arping",
"-I", iface, "-c", "3",
n.ExternalGatewayAddr.String(),
},
Timeout: 6 * time.Second,
PipeOut: true,
PipeErr: true,
})
}
if n.NetworkMode6 == node.Static {
addr := strings.Split(n.ExternalAddrCidr6, "/")[0]
iface := n.SpaceExternalIfaceMod6
if iface == "" {
iface = n.SpaceExternalIface
}
_, _ = commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "ndisc6",
"-r", "3", addr, iface,
},
Timeout: 6 * time.Second,
PipeOut: true,
PipeErr: true,
})
if n.ExternalGatewayAddr6 != nil {
_, _ = commander.Exec(&commander.Opt{
Name: "ip",
Args: []string{
"netns", "exec", n.Namespace, "ping6",
"-c", "3", "-i", "0.5", "-w", "6", "-I", iface,
n.ExternalGatewayAddr6.String(),
},
Timeout: 8 * time.Second,
PipeOut: true,
PipeErr: true,
})
}
}
return
}
func (n *NetConf) ipInit6Alt(db *database.Database) (err error) {
if n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud &&
n.PublicAddress6 != "" && !settings.Hypervisor.NoIpv6PingInit {
addrs, e := utils.DnsLookup(
settings.Hypervisor.DnsServerPrimary6,
"app6.pritunl.com",
)
if e != nil {
logrus.WithFields(logrus.Fields{
"instance_id": n.Virt.Id.Hex(),
"namespace": n.Namespace,
"address6": n.PublicAddress6,
"error": e,
}).Warn("netconf: Failed to initialize IPv6 network DNS lookup")
} else if addrs != nil && len(addrs) > 0 {
output, e := utils.ExecCombinedOutput(
"",
"ip", "netns", "exec", n.Namespace,
"ping6", "-c", "3", "-i", "0.5", "-w", "6", addrs[0],
)
if e != nil {
logrus.WithFields(logrus.Fields{
"instance_id": n.Virt.Id.Hex(),
"namespace": n.Namespace,
"address6": n.PublicAddress6,
"output": output,
}).Warn("netconf: Failed to initialize IPv6 network ping")
}
} else {
logrus.WithFields(logrus.Fields{
"instance_id": n.Virt.Id.Hex(),
"namespace": n.Namespace,
"address6": n.PublicAddress6,
"lookup": addrs,
}).Warn("netconf: Failed to initialize IPv6 network DNS lookup")
}
}
return
}
func (n *NetConf) Ip(db *database.Database) (err error) {
err = n.ipExternal(db)
if err != nil {
return
}
err = n.ipHost(db)
if err != nil {
return
}
err = n.ipNodePort(db)
if err != nil {
return
}
err = n.ipDetect(db)
if err != nil {
return
}
err = n.ipHostIptables(db)
if err != nil {
return
}
err = n.ipDatabase(db)
if err != nil {
return
}
err = n.ipArp(db)
if err != nil {
return
}
err = n.ipInit6(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/netconf.go
================================================
package netconf
import (
"net"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
var (
lock = utils.NewMultiTimeoutLock(2 * time.Minute)
)
type NetConf struct {
Virt *vm.VirtualMachine
Vxlan bool
VlanId int
NetworkMode string
NetworkMode6 string
HostNetwork bool
HostNat bool
HostSubnet string
NodePortNetwork bool
NodePortSubnet string
CloudSubnets set.Set
Namespace string
VmAdapter *vm.NetworkAdapter
PublicAddress string
PublicAddress6 string
CloudVlan int
CloudAddress string
CloudAddressSubnet string
CloudRouterAddress string
CloudAddress6 string
CloudAddressSubnet6 string
CloudRouterAddress6 string
CloudMetal bool
SpaceBridgeIface string
VirtIface string
SpaceExternalIface string
SpaceExternalIfaceMod string
SpaceExternalIfaceMod6 string
SpaceInternalIface string
SpaceHostIface string
SpaceNodePortIface string
SpaceCloudIface string
SpaceCloudVirtIface string
SpaceImdsIface string
BridgeInternalIface string
SystemExternalIface string
SystemInternalIface string
SystemHostIface string
SystemNodePortIface string
PhysicalExternalIface string
PhysicalExternalIfaceBridge bool
PhysicalInternalIface string
PhysicalInternalIfaceBridge bool
PhysicalHostIface string
PhysicalNodePortIface string
SpaceExternalIfaceMtu string
SystemExternalIfaceMtu string
SpaceInternalIfaceMtu string
BridgeInternalIfaceMtu string
SystemInternalIfaceMtu string
SpaceHostIfaceMtu string
SystemHostIfaceMtu string
ImdsIfaceMtu string
SpaceNodePortIfaceMtu string
SystemNodePortIfaceMtu string
VirtIfaceMtu string
InternalAddr net.IP
InternalGatewayAddr net.IP
InternalGatewayAddrCidr string
InternalAddr6 net.IP
InternalGatewayAddr6 net.IP
ExternalVlan int
ExternalAddrCidr string
ExternalGatewayAddr net.IP
ExternalVlan6 int
ExternalAddrCidr6 string
ExternalGatewayAddr6 net.IP
HostAddr net.IP
HostAddrCidr string
HostGatewayAddr net.IP
NodePortAddr net.IP
NodePortAddrCidr string
ExternalMacAddr string
InternalMacAddr string
HostMacAddr string
NodePortMacAddr string
}
func (n *NetConf) Init(db *database.Database) (err error) {
err = n.Validate()
if err != nil {
return
}
err = n.Iface1(db)
if err != nil {
return
}
err = n.Address(db)
if err != nil {
return
}
err = n.Iface2(db, false)
if err != nil {
return
}
err = n.Clear(db)
if err != nil {
return
}
err = n.Base(db)
if err != nil {
return
}
err = n.Oracle(db)
if err != nil {
return
}
err = n.External(db)
if err != nil {
return
}
err = n.Internal(db)
if err != nil {
return
}
err = n.Host(db)
if err != nil {
return
}
err = n.NodePort(db)
if err != nil {
return
}
err = n.Space(db)
if err != nil {
return
}
err = n.Vlan(db)
if err != nil {
return
}
err = n.Bridge(db)
if err != nil {
return
}
err = n.Imds(db)
if err != nil {
return
}
err = n.Ip(db)
if err != nil {
return
}
return
}
func (n *NetConf) Clean(db *database.Database) (err error) {
err = n.Iface1(db)
if err != nil {
return
}
err = n.Iface2(db, true)
if err != nil {
return
}
err = n.ClearAll(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/nodeport.go
================================================
package netconf
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) nodePortNet(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", n.SystemNodePortIface,
"type", "veth",
"peer", "name", n.SpaceNodePortIface,
"addr", n.NodePortMacAddr,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"tc", "qdisc", "replace", "dev", n.SystemNodePortIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
return
}
func (n *NetConf) nodePortMtu(db *database.Database) (err error) {
if n.NodePortNetwork && n.SystemNodePortIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemNodePortIface,
"mtu", n.SystemNodePortIfaceMtu,
)
if err != nil {
return
}
}
if n.NodePortNetwork && n.SpaceNodePortIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SpaceNodePortIface,
"mtu", n.SpaceNodePortIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) nodePortUp(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", n.SystemNodePortIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) nodePortMaster(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", n.SystemNodePortIface,
"master", n.PhysicalNodePortIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) nodePortSpace(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceNodePortIface,
"netns", n.Namespace,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"tc", "qdisc", "replace", "dev", n.SpaceNodePortIface,
"root", "fq_codel",
)
if err != nil {
return err
}
}
return
}
func (n *NetConf) nodePortSpaceUp(db *database.Database) (err error) {
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceNodePortIface, "up",
)
if err != nil {
return
}
}
return
}
func (n *NetConf) NodePort(db *database.Database) (err error) {
delay := time.Duration(settings.Hypervisor.ActionRate) * time.Second
lockId := lock.Lock("nodeport")
defer lock.DelayUnlock("nodeport", lockId, delay)
err = n.nodePortNet(db)
if err != nil {
return
}
err = n.nodePortMtu(db)
if err != nil {
return
}
err = n.nodePortUp(db)
if err != nil {
return
}
err = n.nodePortMaster(db)
if err != nil {
return
}
err = n.nodePortSpace(db)
if err != nil {
return
}
err = n.nodePortSpaceUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/oracle.go
================================================
package netconf
import (
"net"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/oracle"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
func (n *NetConf) oracleInitVnic(db *database.Database) (err error) {
pv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())
if err != nil {
return
}
var vnic *oracle.Vnic
found := false
if n.Virt.CloudVnic != "" {
vnic, err = oracle.GetVnic(pv, n.Virt.CloudVnic)
if err != nil {
if _, ok := err.(*errortypes.NotFoundError); ok {
logrus.WithFields(logrus.Fields{
"vnic_id": n.Virt.CloudVnic,
"error": err,
}).Warn("netconf: Cloud vnic not found, creating new vnic")
err = nil
} else {
return
}
}
if vnic == nil {
found = false
} else if vnic.SubnetId != n.Virt.CloudSubnet {
err = oracle.RemoveVnic(pv, n.Virt.CloudVnicAttach)
if err != nil {
return
}
vnic = nil
} else if !n.CloudSubnets.Contains(vnic.SubnetId) {
err = oracle.RemoveVnic(pv, n.Virt.CloudVnicAttach)
if err != nil {
return
}
vnic = nil
} else {
found = true
}
}
if !n.CloudSubnets.Contains(n.Virt.CloudSubnet) {
err = &errortypes.NotFoundError{
errors.New("netconf: Invalid cloud subnet"),
}
return
}
if !found {
vnicId, vnicAttachId, e := oracle.CreateVnic(
pv, n.Virt.Id.Hex(), n.Virt.CloudSubnet, !n.Virt.NoPublicAddress,
!n.Virt.NoPublicAddress6)
if e != nil {
err = e
return
}
n.Virt.CloudVnic = vnicId
n.Virt.CloudVnicAttach = vnicAttachId
err = n.Virt.CommitCloudVnic(db)
if err != nil {
_ = oracle.RemoveVnic(pv, vnicAttachId)
return
}
}
return
}
func (n *NetConf) oracleConfVnic(db *database.Database) (err error) {
mdata, e := oracle.GetOciMetadata()
if e != nil {
err = e
return
}
n.CloudMetal = mdata.IsBareMetal()
if n.CloudMetal {
err = n.oracleConfVnicMetal(db)
if err != nil {
return
}
} else {
err = n.oracleConfVnicVirt(db)
if err != nil {
return
}
}
return
}
func (n *NetConf) oracleConfVnicMetal(db *database.Database) (err error) {
found := false
nicIndex := 0
macAddr := ""
physicalMacAddr := ""
pv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())
if err != nil {
return
}
for i := 0; i < 120; i++ {
time.Sleep(2 * time.Second)
mdata, e := oracle.GetOciMetadata()
if e != nil {
err = e
return
}
for _, vnic := range mdata.Vnics {
if vnic.Id == n.Virt.CloudVnic {
n.Virt.CloudPrivateIp = vnic.PrivateIp
n.CloudVlan = vnic.VlanTag
n.CloudAddress = vnic.PrivateIp
n.CloudAddressSubnet = vnic.SubnetCidrBlock
n.CloudRouterAddress = vnic.VirtualRouterIp
if len(vnic.Ipv6Addresses) > 0 {
n.CloudAddress6 = vnic.Ipv6Addresses[0]
n.CloudAddressSubnet6 = vnic.Ipv6SubnetCidrBlock
n.CloudRouterAddress6 = vnic.Ipv6VirtualRouterIp
}
nicIndex = vnic.NicIndex
macAddr = strings.ToLower(vnic.MacAddr)
found = true
break
}
}
if found {
break
}
}
if !found {
err = &errortypes.NotFoundError{
errors.New("netconf: Failed to find vnic"),
}
return
}
mdata, err := oracle.GetOciMetadata()
if err != nil {
return
}
found = false
for _, vnic := range mdata.Vnics {
if vnic.NicIndex == nicIndex && vnic.VlanTag == 0 {
physicalMacAddr = strings.ToLower(vnic.MacAddr)
found = true
break
}
}
if !found {
err = &errortypes.NotFoundError{
errors.New("netconf: Failed to find physical nic"),
}
return
}
vnic, err := oracle.GetVnic(pv, n.Virt.CloudVnic)
if err != nil {
return
}
n.Virt.CloudPublicIp = vnic.PublicIp
n.Virt.CloudPublicIp6 = vnic.PublicIp6
err = n.Virt.CommitCloudIps(db)
if err != nil {
return
}
ifaces, err := net.Interfaces()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "netconf: Failed get network interfaces"),
}
return
}
physicalIface := ""
for _, iface := range ifaces {
if strings.ToLower(iface.HardwareAddr.String()) == physicalMacAddr {
physicalIface = iface.Name
break
}
}
if physicalIface == "" {
err = &errortypes.NotFoundError{
errors.New("netconf: Failed to find cloud physical interface"),
}
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"add", "link", physicalIface,
"name", n.SpaceCloudVirtIface,
"address", macAddr,
"type", "macvlan",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceCloudVirtIface,
"netns", n.Namespace,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"add", "link", n.SpaceCloudVirtIface,
"name", n.SpaceCloudIface,
"type", "vlan",
"id", strconv.Itoa(n.CloudVlan),
)
if err != nil {
return
}
return
}
func (n *NetConf) oracleConfVnicVirt(db *database.Database) (err error) {
found := false
cloudMacAddr := ""
pv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())
if err != nil {
return
}
for i := 0; i < 120; i++ {
time.Sleep(2 * time.Second)
mdata, e := oracle.GetOciMetadata()
if e != nil {
err = e
return
}
for _, vnic := range mdata.Vnics {
if vnic.Id == n.Virt.CloudVnic {
n.Virt.CloudPrivateIp = vnic.PrivateIp
n.CloudAddress = vnic.PrivateIp
n.CloudAddressSubnet = vnic.SubnetCidrBlock
n.CloudRouterAddress = vnic.VirtualRouterIp
if len(vnic.Ipv6Addresses) > 0 {
n.CloudAddress6 = vnic.Ipv6Addresses[0]
n.CloudAddressSubnet6 = vnic.Ipv6SubnetCidrBlock
n.CloudRouterAddress6 = vnic.Ipv6VirtualRouterIp
}
cloudMacAddr = strings.ToLower(vnic.MacAddr)
found = true
break
}
}
if found {
break
}
}
if !found {
err = &errortypes.NotFoundError{
errors.New("netconf: Failed to find vnic"),
}
return
}
vnic, err := oracle.GetVnic(pv, n.Virt.CloudVnic)
if err != nil {
return
}
n.Virt.CloudPublicIp = vnic.PublicIp
n.Virt.CloudPublicIp6 = vnic.PublicIp6
err = n.Virt.CommitCloudIps(db)
if err != nil {
return
}
ifaces, err := net.Interfaces()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "netconf: Failed get network interfaces"),
}
return
}
cloudIface := ""
for _, iface := range ifaces {
if strings.ToLower(iface.HardwareAddr.String()) == cloudMacAddr {
cloudIface = iface.Name
break
}
}
if cloudIface == "" {
err = &errortypes.NotFoundError{
errors.New("netconf: Failed to find cloud interface"),
}
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", cloudIface, "down",
)
if err != nil {
return
}
if cloudIface != n.SpaceCloudIface {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev", cloudIface,
"name", n.SpaceCloudIface,
)
if err != nil {
return
}
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "link",
"set", "dev", n.SpaceCloudIface,
"netns", n.Namespace,
)
if err != nil {
return
}
return
}
func (n *NetConf) oracleMtu(db *database.Database) (err error) {
if n.CloudMetal {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceCloudVirtIface,
"mtu", "9000",
)
if err != nil {
return
}
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceCloudIface,
"mtu", "9000",
)
if err != nil {
return
}
return
}
func (n *NetConf) oracleIp(db *database.Database) (err error) {
subnetSplit := strings.Split(n.CloudAddressSubnet, "/")
if len(subnetSplit) != 2 {
err = &errortypes.ParseError{
errors.Newf("netconf: Failed to get cloud cidr %s",
n.CloudAddressSubnet),
}
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.CloudAddress+"/"+subnetSplit[1],
"dev", n.SpaceCloudIface,
)
if err != nil {
return
}
if n.CloudAddress6 != "" {
subnetSplit6 := strings.Split(n.CloudAddressSubnet6, "/")
if len(subnetSplit6) != 2 {
err = &errortypes.ParseError{
errors.Newf("netconf: Failed to get cloud cidr6 %s",
n.CloudAddressSubnet6),
}
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "addr",
"add", n.CloudAddress6+"/"+subnetSplit6[1],
"dev", n.SpaceCloudIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) oracleUp(db *database.Database) (err error) {
if n.CloudMetal {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceCloudVirtIface, "up",
)
if err != nil {
return
}
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.SpaceCloudIface, "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) oracleRoute(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "route",
"add", "default",
"via", n.CloudRouterAddress,
"dev", n.SpaceCloudIface,
)
if err != nil {
return
}
if n.CloudAddress6 != "" && n.CloudRouterAddress6 != "" {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "-6", "route",
"add", "default",
"via", n.CloudRouterAddress6,
"dev", n.SpaceCloudIface,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) Oracle(db *database.Database) (err error) {
if n.NetworkMode != node.Cloud || n.Virt.CloudSubnet == "" {
return
}
delay := time.Duration(settings.Hypervisor.ActionRate) * time.Second
lockId := lock.Lock("oracle")
defer lock.DelayUnlock("oracle", lockId, delay)
err = n.oracleInitVnic(db)
if err != nil {
return
}
err = n.oracleConfVnic(db)
if err != nil {
return
}
err = n.oracleMtu(db)
if err != nil {
return
}
err = n.oracleIp(db)
if err != nil {
return
}
err = n.oracleUp(db)
if err != nil {
return
}
err = n.oracleRoute(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/space.go
================================================
package netconf
import (
"fmt"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/iptables"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) spaceSysctl(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv4.conf.all.accept_redirects=0",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv4.conf.default.accept_redirects=0",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv4.conf.all.rp_filter=1",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv4.conf.default.rp_filter=1",
)
if err != nil {
return
}
if n.HostNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6=1",
n.SpaceHostIface),
)
if err != nil {
return
}
}
if n.NodePortNetwork {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w",
fmt.Sprintf("net.ipv6.conf.%s.disable_ipv6=1",
n.SpaceNodePortIface),
)
if err != nil {
return
}
}
return
}
func (n *NetConf) spaceForward(db *database.Database) (err error) {
if (n.NetworkMode != node.Disabled && n.NetworkMode != node.Cloud) ||
(n.NetworkMode6 != node.Disabled && n.NetworkMode6 != node.Cloud) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"already exists"},
"ip", "netns", "exec", n.Namespace,
"ipset",
"create", "prx_inst6", "hash:net",
"family", "inet6",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"already added"},
"ip", "netns", "exec", n.Namespace,
"ipset",
"add", "prx_inst6", "fe80::/64",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
[]string{"already added"},
"ip", "netns", "exec", n.Namespace,
"ipset",
"add", "prx_inst6", n.InternalAddr6.String()+"/128",
)
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables",
"-I", "FORWARD", "1",
"!", "-d", n.InternalAddr.String()+"/32",
"-i", n.SpaceExternalIface+"+",
"-m", "comment",
"--comment", "pritunl_cloud_base",
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip6tables",
"-I", "FORWARD", "1",
"-m", "set",
"!", "--match-set", "prx_inst6", "dst",
"-i", n.SpaceExternalIface+"+",
"-m", "comment",
"--comment", "pritunl_cloud_base",
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
}
if n.HostNetwork {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables",
"-I", "FORWARD", "1",
"!", "-d", n.InternalAddr.String()+"/32",
"-i", n.SpaceHostIface,
"-m", "comment",
"--comment", "pritunl_cloud_base",
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
}
if n.NodePortNetwork {
iptables.Lock()
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"iptables",
"-I", "FORWARD", "1",
"!", "-d", n.InternalAddr.String()+"/32",
"-i", n.SpaceNodePortIface,
"-m", "comment",
"--comment", "pritunl_cloud_base",
"-j", "DROP",
)
iptables.Unlock()
if err != nil {
return
}
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv4.ip_forward=1",
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"sysctl", "-w", "net.ipv6.conf.all.forwarding=1",
)
if err != nil {
return
}
return
}
func (n *NetConf) spaceVirt(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
"File exists",
},
"ip", "link",
"set", "dev", n.VirtIface,
"netns", n.Namespace,
)
if err != nil {
return
}
return
}
func (n *NetConf) spaceLoopback(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", "lo", "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) spaceMtu(db *database.Database) (err error) {
if n.VirtIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.VirtIface,
"mtu", n.VirtIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) spaceUp(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.VirtIface, "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) Space(db *database.Database) (err error) {
err = n.spaceSysctl(db)
if err != nil {
return
}
err = n.spaceForward(db)
if err != nil {
return
}
err = n.spaceVirt(db)
if err != nil {
return
}
err = n.spaceLoopback(db)
if err != nil {
return
}
err = n.spaceMtu(db)
if err != nil {
return
}
err = n.spaceUp(db)
if err != nil {
return
}
return
}
================================================
FILE: netconf/utils.go
================================================
package netconf
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/oracle"
"github.com/pritunl/pritunl-cloud/vm"
)
func New(virt *vm.VirtualMachine) *NetConf {
return &NetConf{
Virt: virt,
}
}
func Destroy(db *database.Database, virt *vm.VirtualMachine) (err error) {
if virt.CloudVnicAttach == "" {
return
}
pv, err := oracle.NewProvider(node.Self.GetOracleAuthProvider())
if err != nil {
return
}
err = oracle.RemoveVnic(pv, virt.CloudVnicAttach)
if err != nil {
return
}
return
}
================================================
FILE: netconf/validate.go
================================================
package netconf
import (
"net"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/vm"
)
func (n *NetConf) Validate() (err error) {
namespace := vm.GetNamespace(n.Virt.Id, 0)
if len(n.Virt.NetworkAdapters) == 0 {
err = &errortypes.NotFoundError{
errors.New("netconf: Missing network interfaces"),
}
return
}
ifaceNames := set.NewSet()
for i := range n.Virt.NetworkAdapters {
ifaceNames.Add(vm.GetIface(n.Virt.Id, i))
}
for i := range n.Virt.NetworkAdapters {
ifaceNames.Add(vm.GetIface(n.Virt.Id, i))
}
for i := 0; i < 100; i++ {
ifaces, e := net.Interfaces()
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to get network interfaces"),
}
return
}
for _, iface := range ifaces {
if ifaceNames.Contains(iface.Name) {
ifaceNames.Remove(iface.Name)
}
}
if ifaceNames.Len() == 0 {
break
}
ifaces2, e := iproute.IfaceGetAll(namespace)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to get network interfaces"),
}
return
}
for _, iface := range ifaces2 {
if ifaceNames.Contains(iface.Name) {
ifaceNames.Remove(iface.Name)
}
}
if ifaceNames.Len() == 0 {
break
}
time.Sleep(250 * time.Millisecond)
}
if ifaceNames.Len() != 0 {
err = &errortypes.ReadError{
errors.New("qemu: Failed to find network interfaces"),
}
return
}
return
}
================================================
FILE: netconf/vlan.go
================================================
package netconf
import (
"strconv"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func (n *NetConf) vlanNet(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"File exists"},
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"add", "link", n.SpaceInternalIface,
"name", n.BridgeInternalIface,
"type", "vlan",
"id", strconv.Itoa(n.VlanId),
)
if err != nil {
return
}
return
}
func (n *NetConf) vlanMtu(db *database.Database) (err error) {
if n.SpaceInternalIfaceMtu != "" {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.BridgeInternalIface,
"mtu", n.SpaceInternalIfaceMtu,
)
if err != nil {
return
}
}
return
}
func (n *NetConf) vlanUp(db *database.Database) (err error) {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", n.Namespace,
"ip", "link",
"set", "dev", n.BridgeInternalIface, "up",
)
if err != nil {
return
}
return
}
func (n *NetConf) Vlan(db *database.Database) (err error) {
err = n.vlanNet(db)
if err != nil {
return
}
err = n.vlanMtu(db)
if err != nil {
return
}
err = n.vlanUp(db)
if err != nil {
return
}
return
}
================================================
FILE: node/block.go
================================================
package node
import "github.com/pritunl/mongo-go-driver/v2/bson"
type BlockAttachment struct {
Interface string `bson:"interface" json:"interface"`
Block bson.ObjectID `bson:"block" json:"block"`
}
================================================
FILE: node/certificate.go
================================================
package node
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"
"github.com/sirupsen/logrus"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func selfCert(parent *x509.Certificate, parentKey *ecdsa.PrivateKey) (
cert *x509.Certificate, certByt []byte, certKey *ecdsa.PrivateKey,
err error) {
certKey, err = ecdsa.GenerateKey(
elliptic.P384(),
rand.Reader,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "certificate: Failed to generate private key"),
}
return
}
serialLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serial, err := rand.Int(rand.Reader, serialLimit)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(
err,
"certificate: Failed to generate certificate serial",
),
}
return
}
certTempl := &x509.Certificate{
SerialNumber: serial,
Subject: pkix.Name{
Organization: []string{"Pritunl Cloud"},
},
NotBefore: time.Now().Add(-24 * time.Hour),
NotAfter: time.Now().Add(26280 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment |
x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
SignatureAlgorithm: x509.ECDSAWithSHA256,
}
if parent == nil {
parent = certTempl
parentKey = certKey
}
certByt, err = x509.CreateCertificate(rand.Reader, certTempl, parent,
certKey.Public(), parentKey)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "certificate: Failed to create certificate"),
}
return
}
cert, err = x509.ParseCertificate(certByt)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "certificate: Failed to parse certificate"),
}
return
}
return
}
func SelfCert() (certPem, keyPem []byte, err error) {
if Self.SelfCertificate != "" && Self.SelfCertificateKey != "" {
certPem = []byte(Self.SelfCertificate)
keyPem = []byte(Self.SelfCertificateKey)
return
}
logrus.Info("certificate: Generating self signed certificate")
caCert, _, caKey, err := selfCert(nil, nil)
if err != nil {
return
}
_, certByt, certKey, err := selfCert(caCert, caKey)
if err != nil {
return
}
certKeyByte, err := x509.MarshalECPrivateKey(certKey)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "certificate: Failed to parse private key"),
}
return
}
certKeyBlock := &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: certKeyByte,
}
keyPem = pem.EncodeToMemory(certKeyBlock)
certBlock := &pem.Block{
Type: "CERTIFICATE",
Bytes: certByt,
}
certPem = pem.EncodeToMemory(certBlock)
db := database.GetDatabase()
defer db.Close()
Self.SelfCertificate = string(certPem)
Self.SelfCertificateKey = string(keyPem)
err = Self.CommitFields(db, set.NewSet(
"self_certificate", "self_certificate_key"))
if err != nil {
return
}
return
}
================================================
FILE: node/constants.go
================================================
package node
import (
"github.com/dropbox/godropbox/container/set"
)
const (
Admin = "admin"
User = "user"
Balancer = "balancer"
Hypervisor = "hypervisor"
Qemu = "qemu"
Kvm = "kvm"
// std
Std = "std"
// vmware
Vmware = "vmware"
// virtio-vga
Virtio = "virtio"
// virtio-gpu-pci
VirtioPci = "virtio_pci"
// virtio-vga-gl
VirtioVgaGl = "virtio_vga_gl"
// virtio-vga-gl,venus=true
VirtioVgaGlVulkan = "virtio_vga_gl_vulkan"
// virtio-gpu-gl
VirtioGl = "virtio_gl"
// virtio-gpu-gl,venus=true
VirtioGlVulkan = "virtio_gl_vulkan"
// virtio-gpu-gl-pci
VirtioPciGl = "virtio_pci_gl"
// virtio-gpu-gl-pci,venus=true
VirtioPciGlVulkan = "virtio_pci_gl_vulkan"
// virtio-vga prime=1
VirtioPrime = "virtio_prime"
// virtio-gpu-pci prime=1
VirtioPciPrime = "virtio_pci_prime"
// virtio-vga-gl prime=1
VirtioVgaGlPrime = "virtio_vga_gl_prime"
// virtio-vga-gl,venus=true prime=1
VirtioVgaGlVulkanPrime = "virtio_vga_gl_vulkan_prime"
// virtio-gpu-gl prime=1
VirtioGlPrime = "virtio_gl_prime"
// virtio-gpu-gl,venus=true prime=1
VirtioGlVulkanPrime = "virtio_gl_vulkan_prime"
// virtio-gpu-gl-pci prime=1
VirtioPciGlPrime = "virtio_pci_gl_prime"
// virtio-gpu-gl-pci,venus=true prime=1
VirtioPciGlVulkanPrime = "virtio_pci_gl_vulkan_prime"
Sdl = "sdl"
Gtk = "gtk"
Disabled = "disabled"
Dhcp = "dhcp"
DhcpSlaac = "dhcp_slaac"
Slaac = "slaac"
Static = "static"
Internal = "internal"
Cloud = "cloud"
Restart = "restart"
HostPath = "host_path"
)
var (
VgaModes = set.NewSet(
Std,
Vmware,
Virtio,
VirtioPci,
VirtioVgaGl,
VirtioVgaGlVulkan,
VirtioGl,
VirtioGlVulkan,
VirtioPciGl,
VirtioPciGlVulkan,
VirtioPrime,
VirtioPciPrime,
VirtioVgaGlPrime,
VirtioVgaGlVulkanPrime,
VirtioGlPrime,
VirtioGlVulkanPrime,
VirtioPciGlPrime,
VirtioPciGlVulkanPrime,
)
VgaRenderModes = set.NewSet(
VirtioPci,
VirtioVgaGl,
VirtioVgaGlVulkan,
VirtioGl,
VirtioGlVulkan,
VirtioPciGl,
VirtioPciGlVulkan,
VirtioPciPrime,
VirtioVgaGlPrime,
VirtioVgaGlVulkanPrime,
VirtioGlPrime,
VirtioGlVulkanPrime,
VirtioPciGlPrime,
VirtioPciGlVulkanPrime,
)
)
================================================
FILE: node/interfaces.go
================================================
package node
import (
"strings"
"sync"
"time"
"github.com/pritunl/pritunl-cloud/ip"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
netLock = sync.Mutex{}
netIfaces = []ip.Interface{}
netLastIfacesSync time.Time
defaultIface = ""
defaultIfaceSync time.Time
)
func ClearIfaceCache() {
netLastIfacesSync = time.Time{}
netIfaces = []ip.Interface{}
defaultIfaceSync = time.Time{}
defaultIface = ""
}
func GetInterfaces() (ifaces []ip.Interface, err error) {
if time.Since(netLastIfacesSync) < 15*time.Second {
ifaces = netIfaces
return
}
ifacesNew := []ip.Interface{}
allIfaces, err := utils.GetInterfaces()
if err != nil {
return
}
ifacesData, err := ip.GetIfacesCached("")
if err != nil {
return
}
for _, iface := range allIfaces {
if len(iface) == 14 || iface == "lo" ||
strings.Contains(iface, "br") ||
iface == settings.Hypervisor.HostNetworkName ||
iface == settings.Hypervisor.NodePortNetworkName ||
iface == "" {
continue
}
ifaceData := ifacesData[iface]
if ifaceData != nil {
ifacesNew = append(ifacesNew, ip.Interface{
Name: iface,
Address: ifaceData.GetAddress(),
})
} else {
ifacesNew = append(ifacesNew, ip.Interface{
Name: iface,
})
}
}
ifaces = ifacesNew
netLastIfacesSync = time.Now()
netIfaces = ifacesNew
return
}
func getDefaultIface() (iface string, err error) {
if time.Since(defaultIfaceSync) < 900*time.Second {
iface = defaultIface
return
}
output, err := utils.ExecCombinedOutput("", "route", "-n")
if err != nil {
return
}
outputLines := strings.Split(output, "\n")
for _, line := range outputLines {
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
if fields[0] == "0.0.0.0" {
iface = strings.TrimSpace(fields[len(fields)-1])
_ = strings.TrimSpace(fields[1])
}
}
defaultIface = iface
defaultIfaceSync = time.Now()
return
}
================================================
FILE: node/node.go
================================================
package node
import (
"container/list"
"fmt"
"net"
"net/http"
"os"
"os/exec"
"path"
"path/filepath"
"runtime/debug"
"sort"
"strings"
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/advisory"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/bridges"
"github.com/pritunl/pritunl-cloud/cloud"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/drive"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/ip"
"github.com/pritunl/pritunl-cloud/iso"
"github.com/pritunl/pritunl-cloud/lvm"
"github.com/pritunl/pritunl-cloud/pci"
"github.com/pritunl/pritunl-cloud/render"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/telemetry"
"github.com/pritunl/pritunl-cloud/usb"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/zone"
"github.com/sirupsen/logrus"
)
var (
Self *Node
)
type Node struct {
Id bson.ObjectID `bson:"_id" json:"id"`
Datacenter bson.ObjectID `bson:"datacenter,omitempty" json:"datacenter"`
Zone bson.ObjectID `bson:"zone,omitempty" json:"zone"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Types []string `bson:"types" json:"types"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Port int `bson:"port" json:"port"`
Http2 bool `bson:"http2" json:"http2"`
NoRedirectServer bool `bson:"no_redirect_server" json:"no_redirect_server"`
Protocol string `bson:"protocol" json:"protocol"`
Hypervisor string `bson:"hypervisor" json:"hypervisor"`
Vga string `bson:"vga" json:"vga"`
VgaRender string `bson:"vga_render" json:"vga_render"`
AvailableRenders []string `bson:"available_renders" json:"available_renders"`
Gui bool `bson:"gui" json:"gui"`
GuiUser string `bson:"gui_user" json:"gui_user"`
GuiMode string `bson:"gui_mode" json:"gui_mode"`
Certificates []bson.ObjectID `bson:"certificates" json:"certificates"`
SelfCertificate string `bson:"self_certificate_key" json:"-"`
SelfCertificateKey string `bson:"self_certificate" json:"-"`
AdminDomain string `bson:"admin_domain" json:"admin_domain"`
UserDomain string `bson:"user_domain" json:"user_domain"`
WebauthnDomain string `bson:"webauthn_domain" json:"webauthn_domain"`
RequestsMin int64 `bson:"requests_min" json:"requests_min"`
ForwardedForHeader string `bson:"forwarded_for_header" json:"forwarded_for_header"`
ForwardedProtoHeader string `bson:"forwarded_proto_header" json:"forwarded_proto_header"`
ExternalInterfaces []string `bson:"external_interfaces" json:"external_interfaces"`
ExternalInterfaces6 []string `bson:"external_interfaces6" json:"external_interfaces6"`
InternalInterfaces []string `bson:"internal_interfaces" json:"internal_interfaces"`
AvailableInterfaces []ip.Interface `bson:"available_interfaces" json:"available_interfaces"`
AvailableBridges []ip.Interface `bson:"available_bridges" json:"available_bridges"`
AvailableVpcs []*cloud.Vpc `bson:"available_vpcs" json:"available_vpcs"`
CloudSubnets []string `bson:"cloud_subnets" json:"cloud_subnets"`
DefaultInterface string `bson:"default_interface" json:"default_interface"`
NetworkMode string `bson:"network_mode" json:"network_mode"`
NetworkMode6 string `bson:"network_mode6" json:"network_mode6"`
Blocks []*BlockAttachment `bson:"blocks" json:"blocks"`
Blocks6 []*BlockAttachment `bson:"blocks6" json:"blocks6"`
Pools []bson.ObjectID `bson:"pools" json:"pools"`
Shares []*Share `bson:"shares" json:"shares"`
AvailableDrives []*drive.Device `bson:"available_drives" json:"available_drives"`
InstanceDrives []*drive.Device `bson:"instance_drives" json:"instance_drives"`
NoHostNetwork bool `bson:"no_host_network" json:"no_host_network"`
NoNodePortNetwork bool `bson:"no_node_port_network" json:"no_node_port_network"`
HostNat bool `bson:"host_nat" json:"host_nat"`
DefaultNoPublicAddress bool `bson:"default_no_public_address" json:"default_no_public_address"`
DefaultNoPublicAddress6 bool `bson:"default_no_public_address6" json:"default_no_public_address6"`
JumboFrames bool `bson:"jumbo_frames" json:"jumbo_frames"`
JumboFramesInternal bool `bson:"jumbo_frames_internal" json:"jumbo_frames_internal"`
Iscsi bool `bson:"iscsi" json:"iscsi"`
LocalIsos []*iso.Iso `bson:"local_isos" json:"local_isos"`
UsbPassthrough bool `bson:"usb_passthrough" json:"usb_passthrough"`
UsbDevices []*usb.Device `bson:"usb_devices" json:"usb_devices"`
PciPassthrough bool `bson:"pci_passthrough" json:"pci_passthrough"`
PciDevices []*pci.Device `bson:"pci_devices" json:"pci_devices"`
Hugepages bool `bson:"hugepages" json:"hugepages"`
HugepagesSize int `bson:"hugepages_size" json:"hugepages_size"`
Firewall bool `bson:"firewall" json:"firewall"`
Roles []string `bson:"roles" json:"roles"`
Memory float64 `bson:"memory" json:"memory"`
HugePagesUsed float64 `bson:"hugepages_used" json:"hugepages_used"`
Load1 float64 `bson:"load1" json:"load1"`
Load5 float64 `bson:"load5" json:"load5"`
Load15 float64 `bson:"load15" json:"load15"`
CpuUnits int `bson:"cpu_units" json:"cpu_units"`
MemoryUnits float64 `bson:"memory_units" json:"memory_units"`
CpuUnitsRes int `bson:"cpu_units_res" json:"cpu_units_res"`
MemoryUnitsRes float64 `bson:"memory_units_res" json:"memory_units_res"`
Updates []*telemetry.Update `bson:"updates" json:"updates"`
PublicIps []string `bson:"public_ips" json:"public_ips"`
PublicIps6 []string `bson:"public_ips6" json:"public_ips6"`
PrivateIps map[string]string `bson:"private_ips" json:"private_ips"`
SoftwareVersion string `bson:"software_version" json:"software_version"`
Hostname string `bson:"hostname" json:"hostname"`
Version int `bson:"version" json:"-"`
VirtPath string `bson:"virt_path" json:"virt_path"`
CachePath string `bson:"cache_path" json:"cache_path"`
TempPath string `bson:"temp_path" json:"temp_path"`
OracleUser string `bson:"oracle_user" json:"oracle_user"`
OracleTenancy string `bson:"oracle_tenancy" json:"oracle_tenancy"`
OraclePrivateKey string `bson:"oracle_private_key" json:"-"`
OraclePublicKey string `bson:"oracle_public_key" json:"oracle_public_key"`
Operation string `bson:"operation" json:"operation"`
cloudSubnetsNamed []*CloudSubnet `bson:"-" json:"-"`
reqCount *list.List `bson:"-" json:"-"`
dcId bson.ObjectID `bson:"-" json:"-"`
dcZoneId bson.ObjectID `bson:"-" json:"-"`
lock sync.Mutex `bson:"-" json:"-"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id" json:"id"`
Name string `bson:"name" json:"name"`
Zone bson.ObjectID `bson:"zone,omitempty" json:"zone"`
Types []string `bson:"types" json:"types"`
}
func (n *Completion) IsHypervisor() bool {
for _, typ := range n.Types {
if typ == Hypervisor {
return true
}
}
return false
}
type Share struct {
Type string `bson:"type" json:"type"`
Path string `bson:"path" json:"path"`
Roles []string `bson:"roles" json:"roles"`
}
func (s *Share) MatchPath(pth string) bool {
sharePath := utils.FilterPath(s.Path) + string(filepath.Separator)
pth = utils.FilterPath(pth) + string(filepath.Separator)
if sharePath == "" || pth == "" {
return false
}
if sharePath == pth {
return true
}
if !strings.HasPrefix(pth, sharePath) {
return false
}
relPath, err := filepath.Rel(sharePath, pth)
if err != nil {
return false
}
return !strings.HasPrefix(relPath, "..")
}
type CloudSubnet struct {
Id string `json:"id"`
Name string `json:"name"`
}
func (n *Node) Copy() *Node {
n.lock.Lock()
defer n.lock.Unlock()
nde := &Node{
Id: n.Id,
Datacenter: n.Datacenter,
Zone: n.Zone,
Name: n.Name,
Comment: n.Comment,
Types: n.Types,
Timestamp: n.Timestamp,
Port: n.Port,
Http2: n.Http2,
NoRedirectServer: n.NoRedirectServer,
Protocol: n.Protocol,
Hypervisor: n.Hypervisor,
Vga: n.Vga,
VgaRender: n.VgaRender,
Gui: n.Gui,
GuiUser: n.GuiUser,
GuiMode: n.GuiMode,
AvailableRenders: n.AvailableRenders,
Certificates: n.Certificates,
SelfCertificate: n.SelfCertificate,
SelfCertificateKey: n.SelfCertificateKey,
AdminDomain: n.AdminDomain,
UserDomain: n.UserDomain,
WebauthnDomain: n.WebauthnDomain,
RequestsMin: n.RequestsMin,
ForwardedForHeader: n.ForwardedForHeader,
ForwardedProtoHeader: n.ForwardedProtoHeader,
ExternalInterfaces: n.ExternalInterfaces,
ExternalInterfaces6: n.ExternalInterfaces6,
InternalInterfaces: n.InternalInterfaces,
AvailableInterfaces: n.AvailableInterfaces,
AvailableBridges: n.AvailableBridges,
AvailableVpcs: n.AvailableVpcs,
CloudSubnets: n.CloudSubnets,
DefaultInterface: n.DefaultInterface,
NetworkMode: n.NetworkMode,
NetworkMode6: n.NetworkMode6,
Blocks: n.Blocks,
Blocks6: n.Blocks6,
Shares: n.Shares,
Pools: n.Pools,
AvailableDrives: n.AvailableDrives,
InstanceDrives: n.InstanceDrives,
NoHostNetwork: n.NoHostNetwork,
NoNodePortNetwork: n.NoNodePortNetwork,
HostNat: n.HostNat,
DefaultNoPublicAddress: n.DefaultNoPublicAddress,
DefaultNoPublicAddress6: n.DefaultNoPublicAddress6,
JumboFrames: n.JumboFrames,
JumboFramesInternal: n.JumboFramesInternal,
Iscsi: n.Iscsi,
LocalIsos: n.LocalIsos,
UsbPassthrough: n.UsbPassthrough,
UsbDevices: n.UsbDevices,
PciPassthrough: n.PciPassthrough,
PciDevices: n.PciDevices,
Hugepages: n.Hugepages,
HugepagesSize: n.HugepagesSize,
Firewall: n.Firewall,
Roles: n.Roles,
Memory: n.Memory,
HugePagesUsed: n.HugePagesUsed,
Load1: n.Load1,
Load5: n.Load5,
Load15: n.Load15,
CpuUnits: n.CpuUnits,
MemoryUnits: n.MemoryUnits,
CpuUnitsRes: n.CpuUnitsRes,
MemoryUnitsRes: n.MemoryUnitsRes,
Updates: n.Updates,
PublicIps: n.PublicIps,
PublicIps6: n.PublicIps6,
PrivateIps: n.PrivateIps,
SoftwareVersion: n.SoftwareVersion,
Hostname: n.Hostname,
Version: n.Version,
VirtPath: n.VirtPath,
CachePath: n.CachePath,
TempPath: n.TempPath,
OracleUser: n.OracleUser,
OracleTenancy: n.OracleTenancy,
OraclePrivateKey: n.OraclePrivateKey,
OraclePublicKey: n.OraclePublicKey,
Operation: n.Operation,
cloudSubnetsNamed: n.cloudSubnetsNamed,
dcId: n.dcId,
dcZoneId: n.dcZoneId,
}
return nde
}
func (n *Node) AddRequest() {
n.lock.Lock()
back := n.reqCount.Back()
back.Value = back.Value.(int) + 1
n.lock.Unlock()
}
func (n *Node) GetVirtPath() string {
if n.VirtPath == "" {
return constants.DefaultRoot
}
return n.VirtPath
}
func (n *Node) GetCachePath() string {
if n.CachePath == "" {
return constants.DefaultCache
}
return n.CachePath
}
func (n *Node) GetTempPath() string {
if n.TempPath == "" {
return constants.DefaultTemp
}
return n.TempPath
}
func (n *Node) GetDatacenter(db *database.Database) (
dcId bson.ObjectID, err error) {
n.lock.Lock()
if n.Zone == n.dcZoneId {
dcId = n.dcId
n.lock.Unlock()
return
}
n.lock.Unlock()
zne, err := zone.Get(db, n.Zone)
if err != nil {
return
}
dcId = zne.Datacenter
n.lock.Lock()
n.dcId = zne.Datacenter
n.dcZoneId = n.Zone
n.lock.Unlock()
return
}
func (n *Node) GetCloudSubnetsName() (subnets []*CloudSubnet) {
n.lock.Lock()
subnets = n.cloudSubnetsNamed
n.lock.Unlock()
if subnets != nil {
return
}
names := map[string]string{}
if n.AvailableVpcs != nil {
for _, vpc := range n.AvailableVpcs {
for _, subnet := range vpc.Subnets {
names[subnet.Id] = fmt.Sprintf(
"%s - %s", vpc.Name, subnet.Name)
}
}
}
subnets = []*CloudSubnet{}
if n.CloudSubnets != nil {
for _, subnetId := range n.CloudSubnets {
name := names[subnetId]
if name == "" {
name = subnetId
}
subnets = append(subnets, &CloudSubnet{
Id: subnetId,
Name: name,
})
}
}
n.lock.Lock()
n.cloudSubnetsNamed = subnets
n.lock.Unlock()
return
}
func (n *Node) IsAdmin() bool {
for _, typ := range n.Types {
if typ == Admin {
return true
}
}
return false
}
func (n *Node) IsUser() bool {
for _, typ := range n.Types {
if typ == User {
return true
}
}
return false
}
func (n *Node) IsBalancer() bool {
for _, typ := range n.Types {
if typ == Balancer {
return true
}
}
return false
}
func (n *Node) IsHypervisor() bool {
for _, typ := range n.Types {
if typ == Hypervisor {
return true
}
}
return false
}
func (n *Node) IsOnline() bool {
if time.Since(n.Timestamp) > time.Duration(
settings.System.NodeTimestampTtl)*time.Second {
return false
}
return true
}
func (n *Node) IsDhcp() bool {
return n.NetworkMode == Dhcp ||
n.NetworkMode == DhcpSlaac
}
func (n *Node) IsDhcp6() bool {
return n.NetworkMode6 == Dhcp ||
n.NetworkMode6 == DhcpSlaac
}
func (n *Node) Usage() int {
memoryUsage := float64(n.MemoryUnitsRes) / float64(n.MemoryUnits)
if memoryUsage > 1.0 {
memoryUsage = 1.0
}
cpuUsage := float64(n.CpuUnitsRes) / float64(n.CpuUnits)
if cpuUsage > 1.0 {
cpuUsage = 1.0
}
totalUsage := (memoryUsage * 0.75) + (cpuUsage * 0.25)
if totalUsage > 1.0 {
totalUsage = 1.0
}
return int(totalUsage * 100)
}
func (n *Node) SizeResource(memory, processors int) bool {
memoryUnits := float64(memory) / float64(1024)
if memoryUnits+n.MemoryUnitsRes > n.MemoryUnits {
return false
}
if processors+n.CpuUnitsRes > n.CpuUnits*2 {
return false
}
return true
}
func (n *Node) GetOracleAuthProvider() (pv *NodeOracleAuthProvider) {
pv = &NodeOracleAuthProvider{
nde: n,
}
return
}
func (n *Node) GetWebauthn(origin string, strict bool) (
web *webauthn.WebAuthn, err error) {
webauthnDomain := n.WebauthnDomain
if webauthnDomain == "" {
if strict {
err = &errortypes.ReadError{
errors.New("node: Webauthn domain not configured"),
}
return
} else {
userN := strings.Count(n.UserDomain, ".")
adminN := strings.Count(n.AdminDomain, ".")
if userN <= adminN {
webauthnDomain = n.UserDomain
} else {
webauthnDomain = n.AdminDomain
}
}
}
web, err = webauthn.New(&webauthn.Config{
RPDisplayName: "Pritunl Cloud",
RPID: webauthnDomain,
RPOrigins: []string{origin},
})
if err != nil {
err = utils.ParseWebauthnError(err)
return
}
return
}
func (n *Node) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
n.Name = utils.FilterName(n.Name)
if n.Hypervisor == "" {
n.Hypervisor = Kvm
}
if n.Vga == "" {
n.Vga = Virtio
n.VgaRender = ""
}
if !VgaModes.Contains(n.Vga) {
errData = &errortypes.ErrorData{
Error: "node_vga_invalid",
Message: "Invalid VGA type",
}
return
}
if VgaRenderModes.Contains(n.Vga) && n.VgaRender != "" {
found := false
for _, rendr := range n.AvailableRenders {
if n.VgaRender == rendr {
found = true
break
}
}
if !found {
errData = &errortypes.ErrorData{
Error: "node_vga_render_invalid",
Message: "Invalid EGL render",
}
return
}
} else {
n.VgaRender = ""
}
if n.Gui {
if n.GuiUser == "" {
errData = &errortypes.ErrorData{
Error: "gui_user_missing",
Message: "Desktop GUI user must be set",
}
return
}
switch n.GuiMode {
case Sdl, "":
n.GuiMode = Sdl
break
case Gtk:
break
default:
errData = &errortypes.ErrorData{
Error: "gui_mode_invalid",
Message: "Invalid desktop GUI mode",
}
return
}
} else {
n.GuiUser = ""
n.GuiMode = ""
}
if n.Protocol != "http" && n.Protocol != "https" {
errData = &errortypes.ErrorData{
Error: "node_protocol_invalid",
Message: "Invalid node server protocol",
}
return
}
if n.Port < 1 || n.Port > 65535 {
errData = &errortypes.ErrorData{
Error: "node_port_invalid",
Message: "Invalid node server port",
}
return
}
if n.Certificates == nil || n.Protocol != "https" {
n.Certificates = []bson.ObjectID{}
}
if n.Types == nil {
n.Types = []string{}
}
for _, typ := range n.Types {
switch typ {
case Admin, User, Balancer, Hypervisor:
break
default:
errData = &errortypes.ErrorData{
Error: "type_invalid",
Message: "Invalid node type",
}
return
}
}
if !n.IsBalancer() && ((n.IsAdmin() && !n.IsUser()) ||
(n.IsUser() && !n.IsAdmin())) {
n.AdminDomain = ""
n.UserDomain = ""
} else {
if !n.IsAdmin() {
n.AdminDomain = ""
}
if !n.IsUser() {
n.UserDomain = ""
}
}
if !n.Zone.IsZero() {
zne, e := zone.Get(db, n.Zone)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if zne == nil {
n.Zone = bson.NilObjectID
} else {
n.Datacenter = zne.Datacenter
n.Zone = zne.Id
}
}
if n.VirtPath == "" {
n.VirtPath = constants.DefaultRoot
}
if n.CachePath == "" {
n.CachePath = constants.DefaultCache
}
if n.Roles == nil {
n.Roles = []string{}
}
if n.Firewall && len(n.Roles) == 0 {
errData = &errortypes.ErrorData{
Error: "firewall_empty_roles",
Message: "Cannot enable firewall without network roles",
}
return
}
if n.ExternalInterfaces == nil {
n.ExternalInterfaces = []string{}
}
if n.InternalInterfaces == nil {
n.InternalInterfaces = []string{}
}
if n.Blocks == nil {
n.Blocks = []*BlockAttachment{}
}
if n.ExternalInterfaces6 == nil {
n.ExternalInterfaces6 = []string{}
}
if n.Blocks6 == nil {
n.Blocks6 = []*BlockAttachment{}
}
if len(n.InternalInterfaces) == 0 {
errData = &errortypes.ErrorData{
Error: "internal_interface_invalid",
Message: "Missing required internal interface",
}
return
}
if n.CloudSubnets == nil {
n.CloudSubnets = []string{}
}
instanceDrives := []*drive.Device{}
if n.InstanceDrives != nil {
for _, device := range n.InstanceDrives {
device.Id = utils.FilterRelPath(device.Id)
instanceDrives = append(instanceDrives, device)
}
}
n.InstanceDrives = instanceDrives
switch n.NetworkMode {
case Static:
for _, blckAttch := range n.Blocks {
blck, e := block.Get(db, blckAttch.Block)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if blck == nil || blck.Type != block.IPv4 {
errData = &errortypes.ErrorData{
Error: "invalid_block",
Message: "External IPv4 block invalid",
}
return
}
}
break
case Cloud:
n.Blocks = []*BlockAttachment{}
break
case Dhcp:
n.Blocks = []*BlockAttachment{}
break
case Disabled, "":
n.NetworkMode = Disabled
n.Blocks = []*BlockAttachment{}
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_network_mode",
Message: "Network mode invalid",
}
return
}
switch n.NetworkMode6 {
case Static:
for _, blckAttch := range n.Blocks6 {
blck, e := block.Get(db, blckAttch.Block)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if blck == nil || blck.Type != block.IPv6 {
errData = &errortypes.ErrorData{
Error: "invalid_block6",
Message: "External IPv6 block invalid",
}
return
}
}
break
case Cloud:
n.Blocks6 = []*BlockAttachment{}
break
case Dhcp:
n.Blocks6 = []*BlockAttachment{}
break
case Disabled, "":
n.NetworkMode6 = Disabled
n.Blocks6 = []*BlockAttachment{}
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_network_mode6",
Message: "Network mode6 invalid",
}
return
}
if n.NetworkMode == Static && n.NetworkMode6 == Static ||
n.NetworkMode == Disabled && n.NetworkMode6 == Disabled {
n.ExternalInterfaces = []string{}
}
if n.NetworkMode == Cloud || n.NetworkMode6 == Cloud {
if n.OracleUser == "" {
errData = &errortypes.ErrorData{
Error: "missing_oracle_user",
Message: "Oracle user OCID required for host routing",
}
return
}
if n.OracleTenancy == "" {
errData = &errortypes.ErrorData{
Error: "missing_oracle_tenancy",
Message: "Oracle tenancy OCID required for host routing",
}
return
}
} else {
n.OracleUser = ""
n.OracleTenancy = ""
n.CloudSubnets = []string{}
n.AvailableVpcs = []*cloud.Vpc{}
}
newShares := []*Share{}
for _, share := range n.Shares {
if share.Type == "" && share.Path == "" {
continue
}
share.Type = HostPath
share.Path = utils.FilterPath(share.Path)
if share.Path == "" {
errData = &errortypes.ErrorData{
Error: "share_path_invalid",
Message: "Invalid share path",
}
return
}
if len(share.Roles) == 0 {
errData = &errortypes.ErrorData{
Error: "missing_share_path_roles",
Message: "Share path missing required roles",
}
return
}
newShares = append(newShares, share)
}
n.Shares = newShares
n.Format()
return
}
func (n *Node) Format() {
sort.Strings(n.Types)
utils.SortObjectIds(n.Certificates)
}
func (n *Node) JsonHypervisor() {
vpcs := []*cloud.Vpc{}
oracleSubnets := set.NewSet()
for _, subnet := range n.CloudSubnets {
oracleSubnets.Add(subnet)
}
for _, vpc := range n.AvailableVpcs {
subnets := []*cloud.Subnet{}
for _, subnet := range vpc.Subnets {
if oracleSubnets.Contains(subnet.Id) {
subnets = append(subnets, subnet)
}
}
if len(subnets) > 0 {
vpcs = append(vpcs, vpc)
}
}
n.AvailableVpcs = vpcs
return
}
func (n *Node) SetActive() {
if time.Since(n.Timestamp) > 30*time.Second {
n.RequestsMin = 0
n.Memory = 0
n.Load1 = 0
n.Load5 = 0
n.Load15 = 0
n.CpuUnits = 0
n.CpuUnitsRes = 0
n.MemoryUnits = 0
n.MemoryUnitsRes = 0
}
}
func (n *Node) Commit(db *database.Database) (err error) {
coll := db.Nodes()
err = coll.Commit(n.Id, n)
if err != nil {
return
}
return
}
func (n *Node) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Nodes()
err = coll.CommitFields(n.Id, n, fields)
if err != nil {
return
}
return
}
func (n *Node) GetStaticAddr(db *database.Database,
instId bson.ObjectID) (blck *block.Block, ip net.IP, iface string,
err error) {
blck, blckIp, err := block.GetInstanceIp(db, instId, block.External)
if err != nil {
return
}
if blckIp != nil {
for _, blckAttch := range n.Blocks {
if blckAttch.Block == blck.Id {
ip = blckIp.GetIp()
iface = blckAttch.Interface
return
}
}
err = block.RemoveIp(db, blckIp.Id)
if err != nil {
return
}
}
for _, blckAttch := range n.Blocks {
blck, err = block.Get(db, blckAttch.Block)
if err != nil {
return
}
iface = blckAttch.Interface
ip, err = blck.GetIp(db, instId, block.External)
if err != nil {
if _, ok := err.(*block.BlockFull); ok {
err = nil
continue
} else {
return
}
}
break
}
if ip == nil {
err = &errortypes.NotFoundError{
errors.New("node: No external block addresses available"),
}
return
}
return
}
func (n *Node) GetStaticAddr6(db *database.Database,
instId bson.ObjectID, vlan int, matchIface string) (
blck *block.Block, ip net.IP, cidr int, iface string, err error) {
mismatch := false
if matchIface != "" {
for _, blckAttch := range n.Blocks6 {
if blckAttch.Interface != matchIface {
mismatch = true
continue
}
blck, err = block.Get(db, blckAttch.Block)
if err != nil {
return
}
iface = blckAttch.Interface
ip, cidr, err = blck.GetIp6(db, instId, vlan)
if err != nil {
if _, ok := err.(*block.BlockFull); ok {
err = nil
continue
} else {
return
}
}
break
}
} else {
for _, blckAttch := range n.Blocks6 {
blck, err = block.Get(db, blckAttch.Block)
if err != nil {
return
}
iface = blckAttch.Interface
ip, cidr, err = blck.GetIp6(db, instId, vlan)
if err != nil {
if _, ok := err.(*block.BlockFull); ok {
err = nil
continue
} else {
return
}
}
break
}
}
if ip == nil {
if mismatch {
err = &errortypes.NotFoundError{
errors.New("node: No external block6 with matching " +
"block interface available"),
}
} else {
err = &errortypes.NotFoundError{
errors.New("node: No external block6 addresses available"),
}
}
return
}
return
}
func (n *Node) GetStaticHostAddr(db *database.Database,
instId bson.ObjectID) (blck *block.Block, ip net.IP, err error) {
blck, err = block.GetNodeBlock(n.Id)
if err != nil {
return
}
blckIp, err := block.GetInstanceHostIp(db, instId)
if err != nil {
return
}
if blckIp != nil {
contains, e := blck.Contains(blckIp)
if e != nil {
err = e
return
}
if contains {
ip = blckIp.GetIp()
return
}
err = block.RemoveIp(db, blckIp.Id)
if err != nil {
return
}
}
ip, err = blck.GetIp(db, instId, block.Host)
if err != nil {
if _, ok := err.(*block.BlockFull); ok {
err = nil
} else {
return
}
}
if ip == nil {
err = &errortypes.NotFoundError{
errors.New("node: No host addresses available"),
}
return
}
return
}
func (n *Node) GetStaticNodePortAddr(db *database.Database,
instId bson.ObjectID) (blck *block.Block, ip net.IP, err error) {
blck, err = block.GetNodePortBlock(n.Id)
if err != nil {
return
}
blckIp, err := block.GetInstanceNodePortIp(db, instId)
if err != nil {
return
}
if blckIp != nil {
contains, e := blck.Contains(blckIp)
if e != nil {
err = e
return
}
if contains {
ip = blckIp.GetIp()
return
}
err = block.RemoveIp(db, blckIp.Id)
if err != nil {
return
}
}
ip, err = blck.GetIp(db, instId, block.NodePort)
if err != nil {
if _, ok := err.(*block.BlockFull); ok {
err = nil
} else {
return
}
}
if ip == nil {
err = &errortypes.NotFoundError{
errors.New("node: No node port addresses available"),
}
return
}
return
}
func (n *Node) GetRemoteAddr(r *http.Request) (addr string) {
if n.ForwardedForHeader != "" {
addr = strings.TrimSpace(
strings.SplitN(r.Header.Get(n.ForwardedForHeader), ",", 2)[0])
if addr != "" {
return
}
}
addr = utils.StripPort(r.RemoteAddr)
return
}
func (n *Node) SyncNetwork(clearCache bool) {
netLock.Lock()
defer netLock.Unlock()
if clearCache {
ClearIfaceCache()
bridges.ClearCache()
}
defaultIface, err := getDefaultIface()
if err != nil {
logrus.WithFields(logrus.Fields{
"default_interface": defaultIface,
"error": err,
}).Error("node: Failed to get public address")
}
if defaultIface != "" {
n.DefaultInterface = defaultIface
pubAddr, pubAddr6, err := bridges.GetIpAddrs(defaultIface)
if err != nil {
logrus.WithFields(logrus.Fields{
"default_interface": defaultIface,
"error": err,
}).Error("node: Failed to get public address")
}
if pubAddr != "" {
n.PublicIps = []string{
pubAddr,
}
}
if pubAddr6 != "" {
n.PublicIps6 = []string{
pubAddr6,
}
}
}
privateIps := map[string]string{}
internalInterfaces := n.InternalInterfaces
for _, iface := range internalInterfaces {
addr, _, err := bridges.GetIpAddrs(iface)
if err != nil {
logrus.WithFields(logrus.Fields{
"internal_interface": iface,
"error": err,
}).Error("node: Failed to get private address")
}
if addr != "" {
privateIps[iface] = addr
}
}
n.PrivateIps = privateIps
ifaces, err := GetInterfaces()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get interfaces")
}
if ifaces != nil {
n.AvailableInterfaces = ifaces
} else {
n.AvailableInterfaces = []ip.Interface{}
}
brdgs, err := bridges.GetBridges()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get bridge interfaces")
}
if brdgs != nil {
n.AvailableBridges = brdgs
} else {
n.AvailableBridges = []ip.Interface{}
}
if n.JumboFrames {
n.JumboFramesInternal = true
}
if n.NetworkMode == Cloud || n.NetworkMode6 == Cloud {
oracleVpcs, e := cloud.GetOracleVpcs(n.GetOracleAuthProvider())
if e != nil {
logrus.WithFields(logrus.Fields{
"error": e,
}).Error("node: Failed to get oracle vpcs")
}
if oracleVpcs != nil {
n.AvailableVpcs = oracleVpcs
} else {
n.AvailableVpcs = []*cloud.Vpc{}
}
} else {
n.AvailableVpcs = []*cloud.Vpc{}
}
}
func (n *Node) getUpdateDetails(db *database.Database) (
updates []*telemetry.Update, ok bool) {
updates, ok = telemetry.Updates.Get()
if !ok {
return
}
if len(updates) == 0 {
return
}
detailsMap := map[string][]*advisory.Advisory{}
for _, upd := range n.Updates {
if upd.Advisory != "" && len(upd.Details) > 0 {
detailsMap[upd.Advisory] = upd.Details
}
}
for _, upd := range updates {
if details, ok := detailsMap[upd.Advisory]; ok {
upd.Details = details
}
}
return
}
func (n *Node) update(db *database.Database) (err error) {
coll := db.Nodes()
fields := bson.M{
"timestamp": n.Timestamp,
"requests_min": n.RequestsMin,
"memory": n.Memory,
"hugepages_used": n.HugePagesUsed,
"load1": n.Load1,
"load5": n.Load5,
"load15": n.Load15,
"cpu_units": n.CpuUnits,
"memory_units": n.MemoryUnits,
"cpu_units_res": n.CpuUnitsRes,
"memory_units_res": n.MemoryUnitsRes,
"public_ips": n.PublicIps,
"public_ips6": n.PublicIps6,
"private_ips": n.PrivateIps,
"hostname": n.Hostname,
"local_isos": n.LocalIsos,
"usb_devices": n.UsbDevices,
"pci_devices": n.PciDevices,
"available_renders": n.AvailableRenders,
"available_interfaces": n.AvailableInterfaces,
"available_bridges": n.AvailableBridges,
"available_vpcs": n.AvailableVpcs,
"default_interface": n.DefaultInterface,
"pools": n.Pools,
"available_drives": n.AvailableDrives,
}
updates, ok := n.getUpdateDetails(db)
if ok {
fields["updates"] = updates
}
nde := &Node{}
err = coll.FindOneAndUpdate(
db,
&bson.M{
"_id": n.Id,
},
&bson.M{
"$set": fields,
},
options.FindOneAndUpdate().SetReturnDocument(options.After),
).Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
n.Id = nde.Id
n.Datacenter = nde.Datacenter
n.Zone = nde.Zone
n.Name = nde.Name
n.Comment = nde.Comment
n.Types = nde.Types
n.Port = nde.Port
n.Http2 = nde.Http2
n.NoRedirectServer = nde.NoRedirectServer
n.Protocol = nde.Protocol
n.Hypervisor = nde.Hypervisor
n.Vga = nde.Vga
n.VgaRender = nde.VgaRender
n.Gui = nde.Gui
n.GuiUser = nde.GuiUser
n.GuiMode = nde.GuiMode
n.Certificates = nde.Certificates
n.SelfCertificate = nde.SelfCertificate
n.SelfCertificateKey = nde.SelfCertificateKey
n.AdminDomain = nde.AdminDomain
n.UserDomain = nde.UserDomain
n.WebauthnDomain = nde.WebauthnDomain
n.ForwardedForHeader = nde.ForwardedForHeader
n.ForwardedProtoHeader = nde.ForwardedProtoHeader
n.ExternalInterfaces = nde.ExternalInterfaces
n.ExternalInterfaces6 = nde.ExternalInterfaces6
n.InternalInterfaces = nde.InternalInterfaces
n.CloudSubnets = nde.CloudSubnets
n.NetworkMode = nde.NetworkMode
n.NetworkMode6 = nde.NetworkMode6
n.Blocks = nde.Blocks
n.Blocks6 = nde.Blocks6
n.Shares = nde.Shares
n.InstanceDrives = nde.InstanceDrives
n.NoHostNetwork = nde.NoHostNetwork
n.NoNodePortNetwork = nde.NoNodePortNetwork
n.HostNat = nde.HostNat
n.DefaultNoPublicAddress = nde.DefaultNoPublicAddress
n.DefaultNoPublicAddress6 = nde.DefaultNoPublicAddress6
n.JumboFrames = nde.JumboFrames
n.JumboFramesInternal = nde.JumboFramesInternal
n.Iscsi = nde.Iscsi
n.UsbPassthrough = nde.UsbPassthrough
n.PciPassthrough = nde.PciPassthrough
n.Hugepages = nde.Hugepages
n.HugepagesSize = nde.HugepagesSize
n.Firewall = nde.Firewall
n.Roles = nde.Roles
n.Updates = nde.Updates
n.VirtPath = nde.VirtPath
n.CachePath = nde.CachePath
n.TempPath = nde.TempPath
n.OracleUser = nde.OracleUser
n.OracleTenancy = nde.OracleTenancy
n.OraclePrivateKey = nde.OraclePrivateKey
n.OraclePublicKey = nde.OraclePublicKey
n.Operation = nde.Operation
return
}
func (n *Node) sync() (nde *Node) {
db := database.GetDatabase()
defer db.Close()
nde = n.Copy()
n = nde
n.Timestamp = time.Now()
mem, err := utils.GetMemInfo()
if err != nil {
n.Memory = 0
n.HugePagesUsed = 0
n.MemoryUnits = 0
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get memory")
} else {
n.Memory = utils.ToFixed(mem.UsedPercent, 2)
n.HugePagesUsed = utils.ToFixed(mem.HugePagesUsedPercent, 2)
n.MemoryUnits = utils.ToFixed(
float64(mem.Total)/float64(1048576), 2)
}
load, err := utils.LoadAverage()
if err != nil {
n.CpuUnits = 0
n.Load1 = 0
n.Load5 = 0
n.Load15 = 0
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get load")
} else {
n.CpuUnits = load.CpuUnits
n.Load1 = load.Load1
n.Load5 = load.Load5
n.Load15 = load.Load15
}
n.SyncNetwork(false)
pools, err := lvm.GetAvailablePools(db, n.Zone)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get pools")
}
poolIds := []bson.ObjectID{}
for _, pl := range pools {
poolIds = append(poolIds, pl.Id)
}
n.Pools = poolIds
drives, err := drive.GetDevices()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get drive devices")
n.AvailableDrives = []*drive.Device{}
} else {
n.AvailableDrives = drives
}
renders, err := render.GetRenders()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get renders")
n.AvailableRenders = []string{}
} else {
n.AvailableRenders = renders
}
hostname, err := os.Hostname()
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "node: Failed to get hostname"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get hostname")
}
n.Hostname = hostname
isos, err := iso.GetIsos(path.Join(n.GetVirtPath(), "isos"))
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get isos")
n.LocalIsos = []*iso.Iso{}
} else {
n.LocalIsos = isos
}
if n.UsbPassthrough {
devices, err := usb.GetDevices()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get usb devices")
n.UsbDevices = []*usb.Device{}
} else {
n.UsbDevices = devices
}
} else {
n.UsbDevices = []*usb.Device{}
}
if n.PciPassthrough {
pciDevices, err := pci.GetVfioAll()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to get vfio devices")
n.PciDevices = []*pci.Device{}
} else {
n.PciDevices = pciDevices
}
} else {
n.PciDevices = []*pci.Device{}
}
err = n.update(db)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to update node")
}
if n.Operation == Restart {
logrus.Info("node: Restarting node")
n.Operation = ""
err = n.CommitFields(db, set.NewSet("operation"))
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to commit node operation")
} else {
cmd := exec.Command("systemctl", "restart", "pritunl-cloud")
err = cmd.Start()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("node: Failed to start node restart")
}
}
}
var reqCount *list.List
if Self != nil {
Self.lock.Lock()
reqCount = utils.CopyList(Self.reqCount)
Self.lock.Unlock()
} else {
reqCount = list.New()
for i := 0; i < 60; i++ {
reqCount.PushBack(0)
}
}
var count int64
for elm := reqCount.Front(); elm != nil; elm = elm.Next() {
count += int64(elm.Value.(int))
}
n.RequestsMin = count
reqCount.Remove(reqCount.Front())
reqCount.PushBack(0)
n.reqCount = reqCount
Self = n
return
}
func (n *Node) Init() (err error) {
db := database.GetDatabase()
defer db.Close()
coll := db.Nodes()
err = coll.FindOneId(n.Id, n)
if err != nil {
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
n.SoftwareVersion = constants.Version
if n.Name == "" {
n.Name = utils.RandName()
}
if n.Types == nil {
n.Types = []string{Admin, Hypervisor}
}
if n.Protocol == "" {
n.Protocol = "https"
}
if n.Port == 0 {
n.Port = 443
}
if n.Hypervisor == "" {
n.Hypervisor = Kvm
}
bsonSet := bson.M{
"_id": n.Id,
"name": n.Name,
"types": n.Types,
"timestamp": time.Now(),
"protocol": n.Protocol,
"port": n.Port,
"hypervisor": n.Hypervisor,
"vga": n.Vga,
"software_version": n.SoftwareVersion,
}
if n.OraclePublicKey == "" || n.OraclePrivateKey == "" {
privKey, pubKey, e := utils.GenerateRsaKey()
if e != nil {
err = e
return
}
bsonSet["oracle_public_key"] = strings.TrimSpace(string(pubKey))
bsonSet["oracle_private_key"] = strings.TrimSpace(string(privKey))
}
_, err = coll.UpdateOne(
db,
&bson.M{
"_id": n.Id,
},
&bson.M{
"$set": bsonSet,
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
err = database.ParseError(err)
return
}
n.sync()
event.PublishDispatch(db, "node.change")
go func() {
nde := n
for {
if constants.Shutdown {
return
}
nde = nde.sync()
time.Sleep(1 * time.Second)
}
}()
go func() {
defer func() {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("node: Panic in telemetry")
}
}()
for {
telemetry.Refresh()
time.Sleep(1 * time.Minute)
}
}()
return
}
================================================
FILE: node/oracle.go
================================================
package node
type NodeOracleAuthProvider struct {
nde *Node
}
func (n *NodeOracleAuthProvider) OracleUser() string {
return n.nde.OracleUser
}
func (n *NodeOracleAuthProvider) OracleTenancy() string {
return n.nde.OracleTenancy
}
func (n *NodeOracleAuthProvider) OraclePrivateKey() string {
return n.nde.OraclePrivateKey
}
================================================
FILE: node/utils.go
================================================
package node
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, nodeId bson.ObjectID) (
nde *Node, err error) {
coll := db.Nodes()
nde = &Node{}
err = coll.FindOneId(nodeId, nde)
if err != nil {
return
}
return
}
func GetAll(db *database.Database) (nodes []*Node, err error) {
coll := db.Nodes()
nodes = []*Node{}
cursor, err := coll.Find(db, bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
nde.SetActive()
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (nde *Node, err error) {
coll := db.Nodes()
nde = &Node{}
err = coll.FindOne(db, query).Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNamesMap(db *database.Database, query *bson.M) (
nodeNames map[bson.ObjectID]string, err error) {
coll := db.Nodes()
nodeNames = map[bson.ObjectID]string{}
cursor, err := coll.Find(
db,
query,
options.Find().SetProjection(&bson.D{
{"name", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
nodeNames[nde.Id] = nde.Name
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllHypervisors(db *database.Database, query *bson.M) (
nodes []*Node, err error) {
coll := db.Nodes()
nodes = []*Node{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetProjection(&bson.D{
{"name", 1},
{"types", 1},
{"gui", 1},
{"pools", 1},
{"available_vpcs", 1},
{"cloud_subnets", 1},
{"default_no_public_address", 1},
{"default_no_public_address6", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
if !nde.IsHypervisor() {
continue
}
nde.JsonHypervisor()
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPool(db *database.Database, poolId bson.ObjectID) (
nodes []*Node, err error) {
coll := db.Nodes()
nodes = []*Node{}
cursor, err := coll.Find(
db,
&bson.M{
"pools": poolId,
},
options.Find().SetProjection(&bson.D{
{"name", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (nodes []*Node, count int64, err error) {
coll := db.Nodes()
nodes = []*Node{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
nde.SetActive()
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllShape(db *database.Database, zones []bson.ObjectID,
roles []string) (nodes []*Node, err error) {
coll := db.Nodes()
nodes = []*Node{}
query := &bson.M{
"zone": &bson.M{
"$in": zones,
},
"roles": &bson.M{
"$in": roles,
},
}
cursor, err := coll.Find(
db,
query,
options.Find(),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
if !nde.IsHypervisor() || !nde.IsOnline() {
continue
}
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNet(db *database.Database) (nodes []*Node, err error) {
coll := db.Nodes()
nodes = []*Node{}
cursor, err := coll.Find(db, bson.M{}, options.Find().SetProjection(&bson.D{
{"datacenter", 1},
{"zone", 1},
{"private_ips", 1},
}))
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Node{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
nodes = append(nodes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, nodeId bson.ObjectID) (err error) {
coll := db.Nodes()
_, err = coll.DeleteOne(db, &bson.M{
"_id": nodeId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
================================================
FILE: nodeport/constants.go
================================================
package nodeport
const (
Tcp = "tcp"
Udp = "udp"
)
================================================
FILE: nodeport/mapping.go
================================================
package nodeport
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type Mapping struct {
NodePort bson.ObjectID `bson:"node_port" json:"node_port"`
Protocol string `bson:"protocol" json:"protocol"`
ExternalPort int `bson:"external_port" json:"external_port"`
InternalPort int `bson:"internal_port" json:"internal_port"`
Delete bool `bson:"-" json:"delete"`
}
func (m *Mapping) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
switch m.Protocol {
case Tcp, Udp:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_protocol",
Message: "Invalid node port protocol",
}
return
}
if m.ExternalPort != 0 {
portRanges, e := GetPortRanges()
if e != nil {
err = e
return
}
matched := false
for _, ports := range portRanges {
if ports.Contains(m.ExternalPort) {
matched = true
break
}
}
if !matched {
errData = &errortypes.ErrorData{
Error: "invalid_external_port",
Message: "Invalid external node port",
}
return
}
}
if m.InternalPort <= 0 || m.InternalPort > 65535 {
errData = &errortypes.ErrorData{
Error: "invalid_internal_port",
Message: "Invalid internal node port",
}
return
}
return
}
func (m *Mapping) Diff(mapping *Mapping) bool {
if m.Protocol != mapping.Protocol {
return true
}
if m.ExternalPort != mapping.ExternalPort {
return true
}
if m.InternalPort != mapping.InternalPort {
return true
}
return false
}
================================================
FILE: nodeport/network.go
================================================
package nodeport
import (
"net"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/settings"
)
var (
network *net.IPNet
)
func init() {
module := requires.New("nodeport")
module.After("settings")
module.Handler = func() (err error) {
_, network, err = net.ParseCIDR(settings.Hypervisor.NodePortNetwork)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err,
"nodeport: Failed to parse node port network"),
}
return
}
return
}
}
================================================
FILE: nodeport/nodeport.go
================================================
package nodeport
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type NodePort struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Protocol string `bson:"protocol" json:"protocol"`
Port int `bson:"port" json:"port"`
}
func (n *NodePort) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
switch n.Protocol {
case Tcp, Udp:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_protocol",
Message: "Invalid node port protocol",
}
return
}
if n.Port != 0 {
portRanges, e := GetPortRanges()
if e != nil {
err = e
return
}
matched := false
for _, ports := range portRanges {
if ports.Contains(n.Port) {
matched = true
break
}
}
if !matched {
errData = &errortypes.ErrorData{
Error: "invalid_port",
Message: "Invalid node port",
}
return
}
}
return
}
func (n *NodePort) Sync(db *database.Database) (err error) {
coll := db.Instances()
count, err := coll.CountDocuments(db, &bson.M{
"node_ports.node_port": n.Id,
})
if err != nil {
err = database.ParseError(err)
return
}
if count == 0 {
err = Remove(db, n.Id)
if err != nil {
return
}
}
return
}
func (n *NodePort) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.NodePorts()
err = coll.CommitFields(n.Id, n, fields)
if err != nil {
return
}
return
}
func (n *NodePort) Insert(db *database.Database) (err error) {
coll := db.NodePorts()
resp, err := coll.InsertOne(db, n)
if err != nil {
err = database.ParseError(err)
return
}
n.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: nodeport/utils.go
================================================
package nodeport
import (
"strconv"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/tools/set"
)
type PortRange struct {
Start int
End int
}
func (r *PortRange) Contains(port int) bool {
if port >= r.Start && port <= r.End {
return true
}
return false
}
func Get(db *database.Database, ndePrtId bson.ObjectID) (
ndePrt *NodePort, err error) {
coll := db.NodePorts()
ndePrt = &NodePort{}
err = coll.FindOne(db, &bson.M{
"_id": ndePrtId,
}).Decode(ndePrt)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrg(db *database.Database, orgId, ndePrtId bson.ObjectID) (
ndePrt *NodePort, err error) {
coll := db.NodePorts()
ndePrt = &NodePort{}
err = coll.FindOne(db, &bson.M{
"_id": ndePrtId,
"organization": orgId,
}).Decode(ndePrt)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetPort(db *database.Database, dcId, orgId bson.ObjectID,
protocol string, port int) (ndePrt *NodePort, err error) {
coll := db.NodePorts()
ndePrt = &NodePort{}
err = coll.FindOne(db, &bson.M{
"datacenter": dcId,
"port": port,
}).Decode(ndePrt)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Available(db *database.Database, datacenterId, orgId bson.ObjectID,
protocol string, port int) (available bool, err error) {
ndePrt, err := GetPort(db, datacenterId, orgId, protocol, port)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
available = true
err = nil
return
}
return
}
if ndePrt.Organization == orgId {
available = true
return
}
return
}
func GetPortRanges() (ranges []*PortRange, err error) {
ranges = []*PortRange{}
parts := strings.Split(settings.Hypervisor.NodePortRanges, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
bounds := strings.Split(part, "-")
if len(bounds) != 2 {
err = &errortypes.ParseError{
errors.New("nodeport: Invalid port range format"),
}
return
}
start, e := strconv.Atoi(strings.TrimSpace(bounds[0]))
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "nodeport: Invalid start port"),
}
return
}
end, e := strconv.Atoi(strings.TrimSpace(bounds[1]))
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "nodeport: Invalid end port"),
}
return
}
if start >= end {
err = &errortypes.ParseError{
errors.New("nodeport: Start port larger than end port"),
}
return
}
ranges = append(ranges, &PortRange{
Start: start,
End: end,
})
}
if len(ranges) == 0 {
err = &errortypes.NotFoundError{
errors.New("nodeport: No node ports configured"),
}
return
}
return
}
func New(db *database.Database, dcId, orgId bson.ObjectID,
protocol string, requestPort int) (
ndePrt *NodePort, errData *errortypes.ErrorData, err error) {
maxAttempts := settings.Hypervisor.NodePortMaxAttempts
ranges, err := GetPortRanges()
if err != nil {
return
}
ndPt := &NodePort{
Datacenter: dcId,
Organization: orgId,
Protocol: protocol,
Port: requestPort,
}
errData, err = ndPt.Validate(db)
if err != nil || errData != nil {
return
}
if ndPt.Port != 0 {
err = ndPt.Insert(db)
if err != nil {
return
}
ndePrt = ndPt
return
}
attempted := set.NewSet()
for i := 0; i < maxAttempts; i++ {
selectedRange := ranges[utils.RandInt(0, len(ranges)-1)]
ndPt.Port = utils.RandInt(selectedRange.Start, selectedRange.End)
if attempted.Contains(ndPt.Port) {
i--
continue
}
attempted.Add(ndPt.Port)
err = ndPt.Insert(db)
if err != nil {
if _, ok := err.(*database.DuplicateKeyError); ok {
err = nil
continue
}
return
}
ndePrt = ndPt
break
}
if ndePrt == nil {
err = &errortypes.NotFoundError{
errors.New("nodeport: No available node ports found"),
}
return
}
return
}
func Remove(db *database.Database, ndePrtId bson.ObjectID) (
err error) {
coll := db.NodePorts()
_, err = coll.DeleteOne(db, &bson.M{
"_id": ndePrtId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
================================================
FILE: nonce/nonce.go
================================================
package nonce
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type nonce struct {
Id string `bson:"_id"`
Timestamp time.Time `bson:"timestamp"`
}
func Validate(db *database.Database, nce string) (err error) {
doc := &nonce{
Id: nce,
Timestamp: time.Now(),
}
coll := db.Nonces()
_, err = coll.InsertOne(db, doc)
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.DuplicateKeyError:
err = &errortypes.AuthenticationError{
errors.New("nonce: Duplicate authentication nonce"),
}
break
}
return
}
return
}
================================================
FILE: notification/notification.go
================================================
package notification
import (
"context"
"crypto/tls"
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var (
clientTransport = &http.Transport{
TLSHandshakeTimeout: 10 * time.Second,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
},
}
client = &http.Client{
Transport: clientTransport,
Timeout: 15 * time.Second,
}
)
type notificationResp struct {
Web bool `json:"web"`
Message string `json:"message"`
}
func Check() (err error) {
u := &url.URL{
Scheme: "https",
Host: "app.pritunl.com",
Path: fmt.Sprintf(
"/notification/cloud/%d",
utils.GetIntVer(constants.Version),
),
}
req, err := http.NewRequestWithContext(
context.Background(),
"GET",
u.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "notification: Request init error"),
}
return
}
req.Header.Set("User-Agent", "pritunl-cloud")
res, err := client.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "notification: Request get error"),
}
return
}
defer func() {
_ = res.Body.Close()
}()
if res.StatusCode != 200 {
err = &errortypes.RequestError{
errors.Newf("notification: Bad status %d", res.StatusCode),
}
return
}
data := ¬ificationResp{}
err = json.NewDecoder(res.Body).Decode(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "notification: Failed to parse response body"),
}
return
}
if data.Web {
settings.Local.DisableMsg = utils.FilterStr(data.Message, 256)
logrus.WithFields(logrus.Fields{
"message": settings.Local.DisableMsg,
}).Error("notification: Disabling web server from vulnerability report")
settings.Local.DisableWeb = true
} else {
settings.Local.DisableWeb = false
settings.Local.DisableMsg = ""
}
return
}
================================================
FILE: oracle/iface.go
================================================
package oracle
import (
"strings"
"sync"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Iface struct {
Name string
Address string
Namespace string
MacAddress string
VnicId string
}
var (
ifaceLock = sync.Mutex{}
)
func GetIfaces(logOutput bool) (ifaces []*Iface, err error) {
ifaceLock.Lock()
defer ifaceLock.Unlock()
output, err := utils.ExecCombinedOutputLogged(
[]string{
"does not have",
"invalid metadata",
"cannot locate",
},
"/usr/bin/bash",
"/home/opc/secondary_vnic_all_configure.sh",
)
if err != nil {
return
}
if logOutput {
logrus.WithFields(logrus.Fields{
"output": output,
}).Warn("oracle: Oracle iface output")
}
found := false
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(line)
if len(fields) < 13 {
if found {
break
} else {
continue
}
}
if found && fields[0] == "-" && fields[5] != "-" {
iface := &Iface{
Name: fields[7],
Address: fields[1],
Namespace: fields[5],
MacAddress: fields[11],
VnicId: fields[12],
}
ifaces = append(ifaces, iface)
} else if fields[0] == "CONFIG" && fields[1] == "ADDR" &&
fields[5] == "NS" && fields[7] == "IFACE" &&
fields[11] == "MAC" && fields[12] == "VNIC" {
found = true
continue
}
}
return
}
func ConfIfaces(logOutput bool) (err error) {
ifaceLock.Lock()
defer ifaceLock.Unlock()
output, err := utils.ExecCombinedOutputLogged(
[]string{
"does not have",
"invalid metadata",
"cannot locate",
},
"/usr/bin/bash",
"/home/opc/secondary_vnic_all_configure.sh",
"-c",
"-n",
)
if err != nil {
return
}
if logOutput {
logrus.WithFields(logrus.Fields{
"output": output,
}).Warn("oracle: Oracle iface config output")
}
return
}
================================================
FILE: oracle/metadata.go
================================================
package oracle
import (
"encoding/json"
"strings"
"sync"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
ociLock sync.Mutex
)
type Metadata struct {
UserOcid string
PrivateKey string
RegionName string
TenancyOcid string
CompartmentOcid string
InstanceOcid string
VnicOcid string
}
type OciMetaVnic struct {
Id string `json:"vnicId"`
VlanTag int `json:"vlanTag"`
MacAddr string `json:"macAddr"`
PrivateIp string `json:"privateIp"`
VirtualRouterIp string `json:"virtualRouterIp"`
SubnetCidrBlock string `json:"subnetCidrBlock"`
Ipv6Addresses []string `json:"ipv6Addresses"`
Ipv6SubnetCidrBlock string `json:"ipv6SubnetCidrBlock"`
Ipv6VirtualRouterIp string `json:"ipv6VirtualRouterIp"`
NicIndex int `json:"nicIndex"`
}
type OciMetaInstance struct {
Id string `json:"id"`
DisplayName string `json:"displayName"`
CompartmentId string `json:"compartmentId"`
RegionName string `json:"canonicalRegionName"`
Shape string `json:"shape"`
}
type OciMeta struct {
Instance OciMetaInstance `json:"instance"`
Vnics []OciMetaVnic `json:"vnics"`
}
func (o *OciMeta) IsBareMetal() bool {
if strings.Contains(o.Instance.Shape, "BM.") {
return true
}
return false
}
func GetMetadata(authPv AuthProvider) (mdata *Metadata, err error) {
userOcid := authPv.OracleUser()
tenancyOcid := authPv.OracleTenancy()
privateKey := authPv.OraclePrivateKey()
output, err := utils.ExecOutput("", "oci-metadata", "--json")
if err != nil {
return
}
data := &OciMeta{}
err = json.Unmarshal([]byte(output), data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Failed to parse metadata"),
}
return
}
vnicOcid := ""
if data.Vnics != nil {
for _, vnic := range data.Vnics {
vnicOcid = vnic.Id
break
}
}
if vnicOcid == "" {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Failed to get vnic in metadata"),
}
return
}
mdata = &Metadata{
UserOcid: userOcid,
PrivateKey: privateKey,
RegionName: data.Instance.RegionName,
TenancyOcid: tenancyOcid,
CompartmentOcid: data.Instance.CompartmentId,
InstanceOcid: data.Instance.Id,
VnicOcid: vnicOcid,
}
return
}
func GetOciMetadata() (mdata *OciMeta, err error) {
ociLock.Lock()
defer ociLock.Unlock()
output, err := utils.ExecOutput("", "oci-metadata", "--json")
if err != nil {
return
}
mdata = &OciMeta{}
err = json.Unmarshal([]byte(output), mdata)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Failed to parse metadata"),
}
return
}
return
}
================================================
FILE: oracle/oracle.go
================================================
package oracle
type AuthProvider interface {
OracleUser() string
OracleTenancy() string
OraclePrivateKey() string
}
================================================
FILE: oracle/provider.go
================================================
package oracle
import (
"crypto/rsa"
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
type Provider struct {
Metadata *Metadata
privateKey *rsa.PrivateKey
tenancy string
user string
fingerprint string
region string
compartment string
netClient *core.VirtualNetworkClient
computeClient *core.ComputeClient
}
func (p *Provider) LogInfo() {
logrus.WithFields(logrus.Fields{
"region": p.Metadata.RegionName,
"tenancy": p.Metadata.TenancyOcid,
"compartment": p.Metadata.CompartmentOcid,
"instance": p.Metadata.InstanceOcid,
"instance_vnic": p.Metadata.VnicOcid,
"user": p.Metadata.UserOcid,
"fingerprint": p.fingerprint,
}).Info("oracle: Oracle provider data")
}
func (p *Provider) AuthType() (common.AuthConfig, error) {
return common.AuthConfig{
AuthType: common.UserPrincipal,
IsFromConfigFile: false,
OboToken: nil,
}, nil
}
func (p *Provider) PrivateRSAKey() (*rsa.PrivateKey, error) {
return p.privateKey, nil
}
func (p *Provider) KeyID() (string, error) {
return fmt.Sprintf("%s/%s/%s", p.tenancy, p.user, p.fingerprint), nil
}
func (p *Provider) TenancyOCID() (string, error) {
return p.tenancy, nil
}
func (p *Provider) UserOCID() (string, error) {
return p.user, nil
}
func (p *Provider) KeyFingerprint() (string, error) {
return p.fingerprint, nil
}
func (p *Provider) Region() (string, error) {
return p.region, nil
}
func (p *Provider) CompartmentOCID() (string, error) {
return p.compartment, nil
}
func (p *Provider) GetNetworkClient() (
netClient *core.VirtualNetworkClient, err error) {
if p.netClient != nil {
netClient = p.netClient
return
}
client, err := core.NewVirtualNetworkClientWithConfigurationProvider(p)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to create oracle client"),
}
return
}
p.netClient = &client
netClient = p.netClient
return
}
func (p *Provider) GetComputeClient() (
computeClient *core.ComputeClient, err error) {
if p.computeClient != nil {
computeClient = p.computeClient
return
}
client, err := core.NewComputeClientWithConfigurationProvider(p)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to create oracle client"),
}
return
}
p.computeClient = &client
computeClient = p.computeClient
return
}
func NewProvider(authPv AuthProvider) (prov *Provider, err error) {
mdata, err := GetMetadata(authPv)
if err != nil {
return
}
privateKey, fingerprint, err := loadPrivateKey(mdata)
if err != nil {
return
}
prov = &Provider{
Metadata: mdata,
privateKey: privateKey,
tenancy: mdata.TenancyOcid,
user: mdata.UserOcid,
fingerprint: fingerprint,
region: mdata.RegionName,
compartment: mdata.CompartmentOcid,
}
return
}
================================================
FILE: oracle/routetable.go
================================================
package oracle
import (
"context"
"github.com/dropbox/godropbox/errors"
"github.com/oracle/oci-go-sdk/v65/core"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type RouteTable struct {
Id string
VcnId string
Routes map[string]string
routeRules []core.RouteRule
}
func (r *RouteTable) RouteExists(dest string, nextHopId string) bool {
if r.Routes[dest] == nextHopId {
return true
}
return false
}
func (r *RouteTable) RouteUpsert(dest string, nextHopId string) bool {
for i, routeRule := range r.routeRules {
if routeRule.Destination != nil &&
*routeRule.Destination == dest {
if routeRule.NetworkEntityId != nil &&
*routeRule.NetworkEntityId != nextHopId {
routeRule.NetworkEntityId = &nextHopId
r.routeRules[i] = routeRule
return true
} else {
return false
}
}
}
routeRule := core.RouteRule{
Destination: &dest,
NetworkEntityId: &nextHopId,
}
r.routeRules = append(r.routeRules, routeRule)
return true
}
func (r *RouteTable) CommitRouteRules(pv *Provider) (err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
req := core.UpdateRouteTableRequest{
RtId: &r.Id,
UpdateRouteTableDetails: core.UpdateRouteTableDetails{
RouteRules: r.routeRules,
},
}
_, err = client.UpdateRouteTable(context.Background(), req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to update route table"),
}
return
}
return
}
func GetRouteTables(pv *Provider, vcnId string) (
tables []*RouteTable, err error) {
limit := 100
compartmentId, err := pv.CompartmentOCID()
if err != nil {
return
}
client, err := pv.GetNetworkClient()
if err != nil {
return
}
vnicReq := core.ListRouteTablesRequest{
CompartmentId: &compartmentId,
VcnId: &vcnId,
Limit: &limit,
}
orcTables, err := client.ListRouteTables(context.Background(), vnicReq)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to list route tables"),
}
return
}
tables = []*RouteTable{}
if orcTables.Items != nil {
for _, orcTable := range orcTables.Items {
table := &RouteTable{}
if orcTable.Id != nil {
table.Id = *orcTable.Id
}
if orcTable.VcnId != nil {
table.VcnId = *orcTable.VcnId
}
if orcTable.RouteRules != nil {
table.routeRules = orcTable.RouteRules
} else {
table.routeRules = []core.RouteRule{}
}
routes := map[string]string{}
for _, rule := range table.routeRules {
if rule.Destination == nil || rule.NetworkEntityId == nil {
continue
}
routes[*rule.Destination] = *rule.NetworkEntityId
}
table.Routes = routes
tables = append(tables, table)
}
}
return
}
================================================
FILE: oracle/subnet.go
================================================
package oracle
import (
"context"
"github.com/dropbox/godropbox/errors"
"github.com/oracle/oci-go-sdk/v65/core"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Vcn struct {
Id string
Name string
Network string
Subnets []*Subnet
}
type Subnet struct {
Id string
VcnId string
Name string
Network string
}
func GetSubnet(pv *Provider, subnetId string) (subnet *Subnet, err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
subReq := core.GetSubnetRequest{
SubnetId: &subnetId,
}
orcSubnet, err := client.GetSubnet(context.Background(), subReq)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to get subnet"),
}
return
}
subnet = &Subnet{}
if orcSubnet.Id != nil {
subnet.Id = *orcSubnet.Id
}
if orcSubnet.VcnId != nil {
subnet.VcnId = *orcSubnet.VcnId
}
if orcSubnet.DisplayName != nil {
subnet.Name = *orcSubnet.DisplayName
}
if orcSubnet.CidrBlock != nil {
subnet.Network = *orcSubnet.CidrBlock
}
return
}
func GetVcns(pv *Provider) (vcns []*Vcn, err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
compartmentId, err := pv.CompartmentOCID()
if err != nil {
return
}
req := core.ListVcnsRequest{
CompartmentId: &compartmentId,
Limit: utils.PointerInt(100),
}
orcVcns, err := client.ListVcns(context.Background(), req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to get VCNs"),
}
return
}
vcns = []*Vcn{}
for _, orcVcn := range orcVcns.Items {
vcn := &Vcn{}
if orcVcn.Id != nil {
vcn.Id = *orcVcn.Id
}
if orcVcn.DisplayName != nil {
vcn.Name = *orcVcn.DisplayName
}
if orcVcn.CidrBlock != nil {
vcn.Network = *orcVcn.CidrBlock
}
subnets, e := GetSubnets(pv, vcn.Id)
if e != nil {
err = e
return
}
vcn.Subnets = subnets
vcns = append(vcns, vcn)
}
return
}
func GetSubnets(pv *Provider, vcnId string) (subnets []*Subnet, err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
compartmentId, err := pv.CompartmentOCID()
if err != nil {
return
}
req := core.ListSubnetsRequest{
CompartmentId: &compartmentId,
VcnId: utils.PointerString(vcnId),
Limit: utils.PointerInt(256),
}
orcSubnets, err := client.ListSubnets(context.Background(), req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to get subnets"),
}
return
}
subnets = []*Subnet{}
for _, orcSubnet := range orcSubnets.Items {
subnet := &Subnet{}
if orcSubnet.Id != nil {
subnet.Id = *orcSubnet.Id
}
if orcSubnet.VcnId != nil {
subnet.VcnId = *orcSubnet.VcnId
}
if orcSubnet.DisplayName != nil {
subnet.Name = *orcSubnet.DisplayName
}
if orcSubnet.CidrBlock != nil {
subnet.Network = *orcSubnet.CidrBlock
}
subnets = append(subnets, subnet)
}
return
}
================================================
FILE: oracle/utils.go
================================================
package oracle
import (
"bytes"
"crypto/md5"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func loadPrivateKey(mdata *Metadata) (
key *rsa.PrivateKey, fingerprint string, err error) {
block, _ := pem.Decode([]byte(mdata.PrivateKey))
if block == nil {
err = &errortypes.ParseError{
errors.New("oracle: Failed to decode private key"),
}
return
}
if block.Type != "RSA PRIVATE KEY" {
err = &errortypes.ParseError{
errors.New("oracle: Invalid private key type"),
}
return
}
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Failed to parse rsa key"),
}
return
}
pubKey, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Failed to marshal public key"),
}
return
}
keyHash := md5.New()
keyHash.Write(pubKey)
fingerprint = fmt.Sprintf("%x", keyHash.Sum(nil))
fingerprintBuf := bytes.Buffer{}
for i, run := range fingerprint {
fingerprintBuf.WriteRune(run)
if i%2 == 1 && i != len(fingerprint)-1 {
fingerprintBuf.WriteRune(':')
}
}
fingerprint = fingerprintBuf.String()
return
}
================================================
FILE: oracle/vnic.go
================================================
package oracle
import (
"context"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/oracle/oci-go-sdk/v65/core"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
type Vnic struct {
Id string
SubnetId string
IsPrimary bool
MacAddress string
PrivateIp string
PrivateIpId string
PublicIp string
PublicIp6 string
SkipSourceDestCheck bool
}
func (v *Vnic) SetSkipSourceDestCheck(pv *Provider, val bool) (err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
req := core.UpdateVnicRequest{
VnicId: &v.Id,
UpdateVnicDetails: core.UpdateVnicDetails{
SkipSourceDestCheck: &val,
},
}
_, err = client.UpdateVnic(context.Background(), req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to update vnic"),
}
return
}
return
}
func GetVnic(pv *Provider, vnicId string) (vnic *Vnic, err error) {
client, err := pv.GetNetworkClient()
if err != nil {
return
}
req := core.GetVnicRequest{
VnicId: utils.PointerString(vnicId),
}
orcVnic, err := client.GetVnic(context.Background(), req)
if err != nil {
if orcVnic.RawResponse != nil &&
orcVnic.RawResponse.StatusCode == 404 {
err = &errortypes.NotFoundError{
errors.Wrap(err, "oracle: Failed to find vnic"),
}
return
}
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to get vnic"),
}
return
}
vnic = &Vnic{}
if orcVnic.Id != nil {
vnic.Id = *orcVnic.Id
}
if orcVnic.SubnetId != nil {
vnic.SubnetId = *orcVnic.SubnetId
}
if orcVnic.IsPrimary != nil {
vnic.IsPrimary = *orcVnic.IsPrimary
}
if orcVnic.MacAddress != nil {
vnic.MacAddress = *orcVnic.MacAddress
}
if orcVnic.PrivateIp != nil {
vnic.PrivateIp = *orcVnic.PrivateIp
}
if orcVnic.PublicIp != nil {
vnic.PublicIp = *orcVnic.PublicIp
}
if len(orcVnic.Ipv6Addresses) > 0 {
vnic.PublicIp6 = orcVnic.Ipv6Addresses[0]
}
if orcVnic.SkipSourceDestCheck != nil {
vnic.SkipSourceDestCheck = *orcVnic.SkipSourceDestCheck
}
limit := 10
ipReq := core.ListPrivateIpsRequest{
VnicId: &vnic.Id,
Limit: &limit,
}
orcIps, err := client.ListPrivateIps(context.Background(), ipReq)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to get vnic ips"),
}
return
}
if orcIps.Items != nil {
for _, orcIp := range orcIps.Items {
if orcIp.IsPrimary != nil && *orcIp.IsPrimary &&
orcIp.Id != nil {
vnic.PrivateIpId = *orcIp.Id
break
}
}
}
return
}
func getVnicAttachment(pv *Provider, attachmentId string) (
vnicId string, err error) {
client, err := pv.GetComputeClient()
if err != nil {
return
}
req := core.GetVnicAttachmentRequest{
VnicAttachmentId: utils.PointerString(attachmentId),
}
resp, err := client.GetVnicAttachment(context.Background(), req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to create vnic"),
}
return
}
if resp.VnicId != nil {
vnicId = *resp.VnicId
}
return
}
func CreateVnic(pv *Provider, name, subnetId string,
publicIp, publicIp6 bool) (vnicId, vnicAttachId string, err error) {
client, err := pv.GetComputeClient()
if err != nil {
return
}
req := core.AttachVnicRequest{
AttachVnicDetails: core.AttachVnicDetails{
InstanceId: utils.PointerString(pv.Metadata.InstanceOcid),
DisplayName: utils.PointerString(name),
CreateVnicDetails: &core.CreateVnicDetails{
AssignPublicIp: utils.PointerBool(publicIp),
AssignIpv6Ip: utils.PointerBool(publicIp6),
DisplayName: utils.PointerString(name),
SubnetId: utils.PointerString(subnetId),
},
},
}
var resp core.AttachVnicResponse
retryCount := settings.System.OracleApiRetryCount
retryRate := time.Duration(
settings.System.OracleApiRetryRate) * time.Second
for i := 0; i < retryCount; i++ {
resp, err = client.AttachVnic(context.Background(), req)
if err != nil {
if i != retryCount-1 && resp.RawResponse != nil &&
resp.RawResponse.StatusCode == 409 {
time.Sleep(retryRate)
continue
}
err = &errortypes.RequestError{
errors.Wrap(err, "oracle: Failed to create vnic"),
}
return
}
break
}
if resp.Id == nil {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Nil vnic attachment id"),
}
return
}
vnicAttachId = *resp.Id
for i := 0; i < 60; i++ {
vnicId, err = getVnicAttachment(pv, vnicAttachId)
if err != nil {
time.Sleep(500 * time.Millisecond)
if i == 59 {
return
}
err = nil
continue
}
if vnicId == "" {
time.Sleep(500 * time.Millisecond)
continue
}
break
}
if vnicId == "" {
err = &errortypes.ParseError{
errors.Wrap(err, "oracle: Nil vnic id"),
}
return
}
return
}
func RemoveVnic(pv *Provider, vnicAttachId string) (err error) {
client, err := pv.GetComputeClient()
if err != nil {
return
}
req := core.DetachVnicRequest{
VnicAttachmentId: utils.PointerString(vnicAttachId),
}
retryCount := settings.System.OracleApiRetryCount
retryRate := time.Duration(
settings.System.OracleApiRetryRate) * time.Second
for i := 0; i < retryCount; i++ {
resp, e := client.DetachVnic(context.Background(), req)
if e != nil {
if i != retryCount-1 && resp.RawResponse != nil &&
resp.RawResponse.StatusCode == 409 {
time.Sleep(retryRate)
continue
}
err = &errortypes.RequestError{
errors.Wrap(e, "oracle: Failed to remove vnic"),
}
return
}
break
}
return
}
================================================
FILE: organization/organization.go
================================================
package organization
import (
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Organization struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Roles []string `bson:"roles" json:"roles"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
}
func (d *Organization) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
d.Name = utils.FilterName(d.Name)
if d.Roles == nil {
d.Roles = []string{}
}
return
}
func (d *Organization) Commit(db *database.Database) (err error) {
coll := db.Organizations()
err = coll.Commit(d.Id, d)
if err != nil {
return
}
return
}
func (d *Organization) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Organizations()
err = coll.CommitFields(d.Id, d, fields)
if err != nil {
return
}
return
}
func (c *Organization) Insert(db *database.Database) (err error) {
coll := db.Organizations()
if !c.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("organization: Organization already exists"),
}
return
}
resp, err := coll.InsertOne(db, c)
if err != nil {
err = database.ParseError(err)
return
}
c.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: organization/utils.go
================================================
package organization
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, dcId bson.ObjectID) (
dc *Organization, err error) {
coll := db.Organizations()
dc = &Organization{}
err = coll.FindOneId(dcId, dc)
if err != nil {
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
orgs []*Organization, err error) {
coll := db.Organizations()
orgs = []*Organization{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
org := &Organization{}
err = cursor.Decode(org)
if err != nil {
err = database.ParseError(err)
return
}
orgs = append(orgs, org)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllName(db *database.Database) (orgs []*Organization, err error) {
coll := db.Organizations()
orgs = []*Organization{}
cursor, err := coll.Find(
db,
&bson.M{},
options.Find().
SetSort(bson.D{{"name", 1}}).
SetProjection(bson.D{{"name", 1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
org := &Organization{}
err = cursor.Decode(org)
if err != nil {
err = database.ParseError(err)
return
}
orgs = append(orgs, org)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNameRoles(db *database.Database, roles []string) (
orgs []*Organization, err error) {
coll := db.Organizations()
orgs = []*Organization{}
cursor, err := coll.Find(
db,
&bson.M{
"roles": &bson.M{
"$in": roles,
},
},
options.Find().
SetProjection(bson.D{{"name", 1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
org := &Organization{}
err = cursor.Decode(org)
if err != nil {
err = database.ParseError(err)
return
}
orgs = append(orgs, org)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (orgs []*Organization, count int64, err error) {
coll := db.Organizations()
orgs = []*Organization{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
org := &Organization{}
err = cursor.Decode(org)
if err != nil {
err = database.ParseError(err)
return
}
orgs = append(orgs, org)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, dcId bson.ObjectID) (err error) {
coll := db.Organizations()
_, err = coll.DeleteOne(db, &bson.M{
"_id": dcId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func Count(db *database.Database) (count int64, err error) {
coll := db.Organizations()
count, err = coll.CountDocuments(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: paths/paths.go
================================================
package paths
import (
"crypto/md5"
"encoding/base32"
"encoding/hex"
"fmt"
"path"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
)
func GetVmUuid(instId bson.ObjectID) string {
idHash := md5.New()
idHash.Write(instId[:])
uuid := idHash.Sum(nil)
uuid[6] = (uuid[6] & 0x0f) | uint8((3&0xf)<<4)
uuid[8] = (uuid[8] & 0x3f) | 0x80
buffer := [36]byte{}
hex.Encode(buffer[:], uuid[:4])
buffer[8] = '-'
hex.Encode(buffer[9:13], uuid[4:6])
buffer[13] = '-'
hex.Encode(buffer[14:18], uuid[6:8])
buffer[18] = '-'
hex.Encode(buffer[19:23], uuid[8:10])
buffer[23] = '-'
hex.Encode(buffer[24:], uuid[10:])
return string(buffer[:])
}
func GetVmPath(instId bson.ObjectID) string {
return path.Join(node.Self.GetVirtPath(),
"instances", instId.Hex())
}
func GetDisksPath() string {
return path.Join(node.Self.GetVirtPath(), "disks")
}
func GetLocalIsosPath() string {
return path.Join(node.Self.GetVirtPath(), "isos")
}
func GetBackingPath() string {
return path.Join(node.Self.GetVirtPath(), "backing")
}
func GetTpmsPath() string {
return path.Join(node.Self.GetVirtPath(), "tpms")
}
func GetTpmPath(virtId bson.ObjectID) string {
return path.Join(GetTpmsPath(), virtId.Hex())
}
func GetTpmSockPath(virtId bson.ObjectID) string {
return path.Join(GetTpmsPath(), virtId.Hex(), "sock")
}
func GetTpmPwdPath(virtId bson.ObjectID) string {
return path.Join(GetTpmsPath(), virtId.Hex(), "pwd")
}
func GetTempPath() string {
return node.Self.GetTempPath()
}
func GetTempDir() string {
return path.Join(GetTempPath(), bson.NewObjectID().Hex())
}
func GetDrivePath(driveId string) string {
return path.Join("/dev/disk/by-id", driveId)
}
func GetCachesDir() string {
return path.Join(node.Self.GetVirtPath(), "caches")
}
func GetCacheDir(virtId bson.ObjectID) string {
return path.Join(GetCachesDir(), virtId.Hex())
}
func GetOvmfDir() string {
return path.Join(node.Self.GetVirtPath(), "ovmf")
}
func GetDiskPath(diskId bson.ObjectID) string {
return path.Join(GetDisksPath(),
fmt.Sprintf("%s.qcow2", diskId.Hex()))
}
func GetOvmfVarsPath(virtId bson.ObjectID) string {
return path.Join(GetOvmfDir(),
fmt.Sprintf("%s_vars.fd", virtId.Hex()))
}
func GetDiskTempPath() string {
return path.Join(GetTempPath(),
fmt.Sprintf("disk-%s", bson.NewObjectID().Hex()))
}
func GetImageTempPath() string {
return path.Join(GetTempPath(),
fmt.Sprintf("image-%s", bson.NewObjectID().Hex()))
}
func GetImdsPath() string {
return path.Join(node.Self.GetVirtPath(), "imds")
}
func GetImdsConfPath(instId bson.ObjectID) string {
return path.Join(GetImdsPath(),
fmt.Sprintf("%s-conf.json", instId.Hex()))
}
func GetInstRunPath(instId bson.ObjectID) string {
return path.Join(settings.Hypervisor.RunPath, instId.Hex())
}
func GetImdsSockPath(instId bson.ObjectID) string {
return path.Join(GetInstRunPath(instId), "imds.sock")
}
func GetDiskMountPath() string {
return path.Join(GetTempPath(), bson.NewObjectID().Hex())
}
func GetInitsPath() string {
return path.Join(node.Self.GetVirtPath(), "inits")
}
func GetInitPath(instId bson.ObjectID) string {
return path.Join(GetInitsPath(),
fmt.Sprintf("%s.iso", instId.Hex()))
}
func GetUnitName(virtId bson.ObjectID) string {
return fmt.Sprintf("pritunl_cloud_%s.service", virtId.Hex())
}
func GetUnitPath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.SystemdPath, GetUnitName(virtId))
}
func GetUnitNameDhcp4(virtId bson.ObjectID, n int) string {
return fmt.Sprintf("pritunl_dhcp4_%s_%d.service", virtId.Hex(), n)
}
func GetUnitPathDhcp4(virtId bson.ObjectID, n int) string {
return path.Join(settings.Hypervisor.SystemdPath, GetUnitNameDhcp4(virtId, n))
}
func GetUnitNameDhcp6(virtId bson.ObjectID, n int) string {
return fmt.Sprintf("pritunl_dhcp6_%s_%d.service", virtId.Hex(), n)
}
func GetUnitPathDhcp6(virtId bson.ObjectID, n int) string {
return path.Join(settings.Hypervisor.SystemdPath, GetUnitNameDhcp6(virtId, n))
}
func GetUnitNameNdp(virtId bson.ObjectID, n int) string {
return fmt.Sprintf("pritunl_ndp_%s_%d.service", virtId.Hex(), n)
}
func GetUnitPathNdp(virtId bson.ObjectID, n int) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameNdp(virtId, n))
}
func GetUnitNameTpm(virtId bson.ObjectID) string {
return fmt.Sprintf("pritunl_tpm_%s.service", virtId.Hex())
}
func GetUnitPathTpm(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameTpm(virtId))
}
func GetUnitNameImds(virtId bson.ObjectID) string {
return fmt.Sprintf("pritunl_imds_%s.service", virtId.Hex())
}
func GetUnitNameDhcpc(virtId bson.ObjectID) string {
return fmt.Sprintf("pritunl_dhcpc_%s.service", virtId.Hex())
}
func GetShareId(virtId bson.ObjectID, shareName string) string {
hash := md5.New()
hash.Write([]byte(virtId.Hex()))
hash.Write([]byte(shareName))
return strings.ToLower(base32.StdEncoding.EncodeToString(
hash.Sum(nil))[:12])
}
func GetUnitNameShare(virtId bson.ObjectID, shareId string) string {
return fmt.Sprintf("pritunl_share_%s_%s.service", virtId.Hex(), shareId)
}
func GetUnitNameShares(virtId bson.ObjectID) string {
return fmt.Sprintf("pritunl_share_%s_*.service", virtId.Hex())
}
func GetUnitPathImds(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameImds(virtId))
}
func GetUnitPathDhcpc(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameDhcpc(virtId))
}
func GetUnitPathShare(virtId bson.ObjectID, shareId string) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameShare(virtId, shareId))
}
func GetUnitPathShares(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.SystemdPath,
GetUnitNameShares(virtId))
}
func GetPidPath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.RunPath,
fmt.Sprintf("%s.pid", virtId.Hex()))
}
func GetShareSockPath(virtId bson.ObjectID, shareId string) string {
return path.Join(GetInstRunPath(virtId),
fmt.Sprintf("virtiofs_%s.sock", shareId))
}
func GetHugepagePath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.HugepagesPath, virtId.Hex())
}
func GetSockPath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.RunPath,
fmt.Sprintf("%s.sock", virtId.Hex()))
}
func GetQmpSockPath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.RunPath,
fmt.Sprintf("%s.qmp.sock", virtId.Hex()))
}
func GetGuestPath(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.RunPath,
fmt.Sprintf("%s.guest", virtId.Hex()))
}
// TODO Backward compatibility
func GetPidPathOld(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.LibPath,
fmt.Sprintf("%s.pid", virtId.Hex()))
}
// TODO Backward compatibility
func GetSockPathOld(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.LibPath,
fmt.Sprintf("%s.sock", virtId.Hex()))
}
// TODO Backward compatibility
func GetQmpSockPathOld(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.LibPath,
fmt.Sprintf("%s.qmp.sock", virtId.Hex()))
}
// TODO Backward compatibility
func GetGuestPathOld(virtId bson.ObjectID) string {
return path.Join(settings.Hypervisor.LibPath,
fmt.Sprintf("%s.guest", virtId.Hex()))
}
func GetNamespacesPath() string {
return "/etc/netns"
}
func GetNamespacePath(namespace string) string {
return path.Join(GetNamespacesPath(), namespace)
}
================================================
FILE: paths/utils.go
================================================
package paths
import (
"os"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
)
var (
ovmfCodePaths = []string{
"/usr/share/edk2/ovmf/OVMF_CODE.fd",
"/usr/share/edk2/ovmf/OVMF_CODE.cc.fd",
"/usr/share/OVMF/OVMF_CODE.pure-efi.fd",
"/usr/share/OVMF/OVMF_CODE.fd",
}
ovmfVarsPaths = []string{
"/usr/share/edk2/ovmf/OVMF_VARS.fd",
"/usr/share/OVMF/OVMF_VARS.pure-efi.fd",
"/usr/share/OVMF/OVMF_VARS.fd",
}
ovmfSecureCodePaths = []string{
"/usr/share/edk2/ovmf/OVMF_CODE.secboot.fd",
"/usr/share/OVMF/OVMF_CODE.secboot.fd",
}
ovmfSecureVarsPaths = []string{
"/usr/share/edk2/ovmf/OVMF_VARS.secboot.fd",
"/usr/share/OVMF/OVMF_VARS.secboot.fd",
"/usr/share/OVMF/OVMF_VARS.fd",
}
)
func existsFile(pth string) (exists bool, err error) {
_, err = os.Stat(pth)
if err == nil {
exists = true
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "paths: Failed to stat %s", pth),
}
return
}
func FindOvmfCodePath(secureBoot bool) (pth string, err error) {
if secureBoot {
pth = settings.Hypervisor.OvmfSecureCodePath
if pth != "" {
return
}
for _, pth = range ovmfSecureCodePaths {
exists, e := existsFile(pth)
if e != nil {
err = e
return
}
if exists {
return
}
}
} else {
pth = settings.Hypervisor.OvmfCodePath
if pth != "" {
return
}
for _, pth = range ovmfCodePaths {
exists, e := existsFile(pth)
if e != nil {
err = e
return
}
if exists {
return
}
}
}
pth = ""
err = &errortypes.NotFoundError{
errors.New("paths: Failed to find OVMF code file"),
}
return
}
func FindOvmfVarsPath(secureBoot bool) (pth string, err error) {
if secureBoot {
pth = settings.Hypervisor.OvmfSecureVarsPath
if pth != "" {
return
}
for _, pth = range ovmfSecureVarsPaths {
exists, e := existsFile(pth)
if e != nil {
err = e
return
}
if exists {
return
}
}
} else {
pth = settings.Hypervisor.OvmfVarsPath
if pth != "" {
return
}
for _, pth = range ovmfVarsPaths {
exists, e := existsFile(pth)
if e != nil {
err = e
return
}
if exists {
return
}
}
}
pth = ""
err = &errortypes.NotFoundError{
errors.New("paths: Failed to find OVMF vars file"),
}
return
}
================================================
FILE: pci/pci.go
================================================
package pci
import (
"sync"
"time"
)
var (
syncLast time.Time
syncLock sync.Mutex
syncCache []*Device
)
type Device struct {
Slot string `bson:"slot" json:"slot"`
Class string `bson:"class" json:"class"`
Name string `bson:"name" json:"name"`
Driver string `bson:"driver" json:"driver"`
}
================================================
FILE: pci/utils.go
================================================
package pci
import (
"regexp"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
reg = regexp.MustCompile(
"[a-fA-F0-9][a-fA-F0-9]:[a-fA-F0-9][a-fA-F0-9].[0-9]")
)
func CheckSlot(slot string) bool {
return reg.MatchString(slot)
}
func GetVfio(slot string) (dev *Device, err error) {
devices, err := GetVfioAll()
if err != nil {
return
}
for _, device := range devices {
if device.Slot == slot {
dev = device
return
}
}
return
}
func GetVfioAll() (devices []*Device, err error) {
if time.Since(syncLast) < 30*time.Second {
devices = syncCache
return
}
syncLock.Lock()
defer syncLock.Unlock()
devices = []*Device{}
output, err := utils.ExecOutput("", "lspci", "-v")
if err != nil {
return
}
dev := &Device{}
outputLines := strings.Split(output, "\n")
for _, line := range outputLines {
if strings.TrimSpace(line) == "" {
if dev.Slot != "" && dev.Name != "" &&
dev.Driver == "vfio-pci" && CheckSlot(dev.Slot) {
devices = append(devices, dev)
}
dev = &Device{}
continue
}
if dev.Slot == "" {
lines := strings.SplitN(line, " ", 2)
if len(lines) != 2 {
continue
}
names := strings.SplitN(lines[1], ":", 2)
if len(names) != 2 {
continue
}
dev.Slot = strings.TrimSpace(lines[0])
dev.Class = strings.TrimSpace(names[0])
dev.Name = strings.TrimSpace(names[1])
} else if strings.Contains(line, "Kernel driver in use:") {
lines := strings.SplitN(line, ":", 2)
if len(lines) != 2 {
continue
}
dev.Driver = strings.TrimSpace(lines[1])
}
}
syncCache = devices
syncLast = time.Now()
return
}
================================================
FILE: permission/permission.go
================================================
package permission
import (
"fmt"
"os"
"path/filepath"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
func chown(virt *vm.VirtualMachine, path string) (err error) {
err = os.Chown(path, virt.UnixId, 0)
if err != nil {
err = &errortypes.WriteError{
errors.Newf(
"permission: Failed to set owner of '%s' to '%d'",
path, virt.UnixId,
),
}
return
}
return
}
func touchChown(virt *vm.VirtualMachine, path string) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"touch", path,
)
if err != nil {
return
}
err = chown(virt, path)
if err != nil {
return
}
return
}
func mkdirChown(virt *vm.VirtualMachine, path string) (err error) {
_, err = utils.ExecCombinedOutputLogged(nil,
"mkdir", "-p", path,
)
if err != nil {
return
}
err = chown(virt, path)
if err != nil {
return
}
return
}
func Restore(pth string) (err error) {
err = os.Chown(pth, 0, 0)
if err != nil {
err = &errortypes.WriteError{
errors.Newf(
"permission: Failed to set owner of '%s' to '0'", pth,
),
}
return
}
return
}
func Chown(virt *vm.VirtualMachine, pth string) (err error) {
err = chown(virt, pth)
if err != nil {
return
}
return
}
func InitVirt(virt *vm.VirtualMachine) (err error) {
err = UserAdd(virt)
if err != nil {
return
}
if virt.Uefi {
err = chown(virt, paths.GetOvmfVarsPath(virt.Id))
if err != nil {
return
}
}
err = chown(virt, paths.GetInitPath(virt.Id))
if err != nil {
return
}
for _, disk := range virt.Disks {
err = chown(virt, disk.Path)
if err != nil {
return
}
}
for _, device := range virt.DriveDevices {
drivePth := ""
if device.Type == vm.Lvm {
drivePth = filepath.Join("/dev/mapper",
fmt.Sprintf("%s-%s", device.VgName, device.LvName))
} else {
drivePth = paths.GetDrivePath(device.Id)
}
err = chown(virt, drivePth)
if err != nil {
return
}
}
for _, device := range virt.UsbDevices {
usbDevice, _ := device.GetDevice()
if usbDevice != nil {
err = Chown(virt, usbDevice.BusPath)
if err != nil {
return
}
}
}
err = chown(virt, paths.GetCacheDir(virt.Id))
if err != nil {
return
}
return
}
func InitDisk(virt *vm.VirtualMachine, dsk *vm.Disk) (err error) {
err = UserAdd(virt)
if err != nil {
return
}
err = chown(virt, dsk.Path)
if err != nil {
return
}
return
}
func InitTpm(virt *vm.VirtualMachine) (err error) {
tpmPath := paths.GetTpmPath(virt.Id)
err = chown(virt, tpmPath)
if err != nil {
return
}
return
}
func InitTpmPwd(virt *vm.VirtualMachine) (err error) {
tpmPath := paths.GetTpmPwdPath(virt.Id)
err = chown(virt, tpmPath)
if err != nil {
return
}
return
}
func InitImds(virt *vm.VirtualMachine) (err error) {
runPath := paths.GetInstRunPath(virt.Id)
err = chown(virt, runPath)
if err != nil {
return
}
return
}
func InitMount(virt *vm.VirtualMachine, shareId string) (err error) {
sockPath := paths.GetShareSockPath(virt.Id, shareId)
err = chown(virt, sockPath)
if err != nil {
return
}
return
}
================================================
FILE: permission/user.go
================================================
package permission
import (
"fmt"
"os/user"
"path"
"strconv"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
func GetUserName(vmId bson.ObjectID) string {
return fmt.Sprintf("pritunl-%s", vmId.Hex())
}
func UserAdd(virt *vm.VirtualMachine) (err error) {
name := GetUserName(virt.Id)
usr, e := user.LookupId(strconv.Itoa(virt.UnixId))
if usr != nil && e == nil {
return
}
if virt.UnixId == 0 {
err = &errortypes.ParseError{
errors.New("permission: Virt missing unix id"),
}
return
}
_, err = utils.ExecCombinedOutputLogged(nil,
"useradd",
"--no-user-group",
"--no-create-home",
"--uid", strconv.Itoa(virt.UnixId),
name,
)
if err != nil {
return
}
mailPath := path.Join("/var/mail", name)
_ = utils.RemoveAll(mailPath)
return
}
func UserDelete(virt *vm.VirtualMachine) (err error) {
name := GetUserName(virt.Id)
_, _ = utils.ExecCombinedOutput("",
"userdel",
name,
)
_, _ = utils.ExecCombinedOutput("",
"groupdel",
name,
)
return
}
func UserGroupAdd(virtId bson.ObjectID, group string) (err error) {
name := GetUserName(virtId)
_, err = utils.ExecCombinedOutputLogged(
[]string{
"does not exist",
},
"gpasswd",
"-a", name,
group,
)
return
}
func UserGroupDelete(virtId bson.ObjectID, group string) (err error) {
name := GetUserName(virtId)
_, err = utils.ExecCombinedOutputLogged(
[]string{
"not a member",
"does not exist",
},
"gpasswd",
"-d", name,
group,
)
return
}
================================================
FILE: plan/constants.go
================================================
package plan
import (
"github.com/dropbox/godropbox/container/set"
)
const (
Start = "start"
Stop = "stop"
Restart = "restart"
Destroy = "destroy"
)
var actions = set.NewSet(
Start,
Stop,
Restart,
Destroy,
)
================================================
FILE: plan/data.go
================================================
package plan
import (
"encoding/json"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/eval"
)
type Data struct {
Unit Unit `json:"unit"`
Instance Instance `json:"instance"`
}
type Unit struct {
Name string `json:"name"`
Count int `json:"count"`
}
type Instance struct {
Name string `json:"name"`
State string `json:"state"`
Action string `json:"action"`
Processors int `json:"processors"`
Memory int `json:"memory"`
LastTimestamp int `json:"last_timestamp"`
LastHeartbeat int `json:"last_heartbeat"`
}
func (d *Data) Export() (data eval.Data, err error) {
dataByt, err := json.Marshal(d)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "plan: Failed to marshal"),
}
return
}
data = eval.Data{}
err = json.Unmarshal(dataByt, &data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "plan: Failed to unmarshal"),
}
return
}
return
}
func GetEmtpyData() (data eval.Data, err error) {
dataStrct := Data{}
data, err = dataStrct.Export()
if err != nil {
return
}
return
}
================================================
FILE: plan/plan.go
================================================
package plan
import (
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/eval"
"github.com/pritunl/pritunl-cloud/utils"
)
type Plan struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Statements []*Statement `bson:"statements" json:"statements"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
}
type Statement struct {
Id bson.ObjectID `bson:"id" json:"id"`
Statement string `bson:"statement" json:"statement"`
}
func (p *Plan) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
p.Name = utils.FilterName(p.Name)
if p.Organization.IsZero() {
errData = &errortypes.ErrorData{
Error: "organization_required",
Message: "Missing required organization",
}
return
}
emptyData, err := GetEmtpyData()
if err != nil {
return
}
if p.Statements == nil {
p.Statements = []*Statement{}
}
for _, statement := range p.Statements {
if statement.Id.IsZero() {
statement.Id = bson.NewObjectID()
}
err = eval.Validate(statement.Statement)
if err != nil {
return
}
_, _, err = eval.Eval(emptyData, statement.Statement)
if err != nil {
return
}
}
return
}
func (p *Plan) UpdateStatements(inStatements []*Statement) (err error) {
curStatements := map[bson.ObjectID]*Statement{}
for _, statement := range p.Statements {
curStatements[statement.Id] = statement
}
newStatements := []*Statement{}
for _, statement := range inStatements {
curStatement := curStatements[statement.Id]
if curStatement != nil {
if statement.Statement == curStatement.Statement {
newStatements = append(newStatements, curStatement)
} else {
newStatement := &Statement{
Id: bson.NewObjectID(),
Statement: statement.Statement,
}
newStatements = append(newStatements, newStatement)
}
} else {
newStatement := &Statement{
Id: bson.NewObjectID(),
Statement: statement.Statement,
}
newStatements = append(newStatements, newStatement)
}
}
p.Statements = newStatements
return
}
func (p *Plan) Commit(db *database.Database) (err error) {
coll := db.Plans()
err = coll.Commit(p.Id, p)
if err != nil {
return
}
return
}
func (p *Plan) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Plans()
err = coll.CommitFields(p.Id, p, fields)
if err != nil {
return
}
return
}
func (p *Plan) Insert(db *database.Database) (err error) {
coll := db.Plans()
if !p.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("domain: Plan already exists"),
}
return
}
_, err = coll.InsertOne(db, p)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: plan/utils.go
================================================
package plan
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, plnId bson.ObjectID) (
pln *Plan, err error) {
coll := db.Plans()
pln = &Plan{}
err = coll.FindOneId(plnId, pln)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, plnId bson.ObjectID) (
pln *Plan, err error) {
coll := db.Plans()
pln = &Plan{}
err = coll.FindOne(db, &bson.M{
"_id": plnId,
"organization": orgId,
}).Decode(pln)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func ExistsOrg(db *database.Database, orgId, plnId bson.ObjectID) (
exists bool, err error) {
coll := db.Plans()
n, err := coll.CountDocuments(db, &bson.M{
"_id": plnId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
if n > 0 {
exists = true
}
return
}
func GetOne(db *database.Database, query *bson.M) (pln *Plan, err error) {
coll := db.Plans()
pln = &Plan{}
err = coll.FindOne(db, query).Decode(pln)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
plns []*Plan, err error) {
coll := db.Plans()
plns = []*Plan{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
dmn := &Plan{}
err = cursor.Decode(dmn)
if err != nil {
err = database.ParseError(err)
return
}
plns = append(plns, dmn)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (plns []*Plan, count int64, err error) {
coll := db.Plans()
plns = []*Plan{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
defer cursor.Close(db)
for cursor.Next(db) {
dmn := &Plan{}
err = cursor.Decode(dmn)
if err != nil {
err = database.ParseError(err)
return
}
plns = append(plns, dmn)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllName(db *database.Database, query *bson.M) (
plns []*Plan, err error) {
coll := db.Plans()
plns = []*Plan{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetProjection(bson.D{
{"name", 1},
{"organization", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
dmn := &Plan{}
err = cursor.Decode(dmn)
if err != nil {
err = database.ParseError(err)
return
}
plns = append(plns, dmn)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, plnId bson.ObjectID) (err error) {
coll := db.Plans()
_, err = coll.DeleteOne(db, &bson.M{
"_id": plnId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveOrg(db *database.Database, orgId, plnId bson.ObjectID) (
err error) {
coll := db.Plans()
_, err = coll.DeleteOne(db, &bson.M{
"_id": plnId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, plnIds []bson.ObjectID) (err error) {
coll := db.Plans()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": plnIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
plnIds []bson.ObjectID) (err error) {
coll := db.Plans()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": plnIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: planner/planner.go
================================================
package planner
import (
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/eval"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/plan"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Planner struct {
unitsMap map[bson.ObjectID]*unit.Unit
}
func (p *Planner) setInstanceAction(db *database.Database,
deply *deployment.Deployment, inst *instance.Instance,
statement *plan.Statement, threshold int, action string) (err error) {
disks, e := disk.GetInstance(db, inst.Id)
if e != nil {
err = e
return
}
for _, dsk := range disks {
if dsk.Action != "" {
logrus.WithFields(logrus.Fields{
"instance_id": inst.Id.Hex(),
"disk_id": dsk.Id.Hex(),
"disk_action": dsk.Action,
}).Info("deploy: Ignoring instance plan action, " +
"disk action pending")
return
}
}
if inst.Action == action {
return
}
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
"statement": statement.Statement,
"threshold": threshold,
"action": action,
}).Info("scheduler: Handling plan action")
inst.Action = action
errData, e := inst.Validate(db)
if e != nil {
err = e
return
}
if errData != nil {
err = errData.GetError()
return
}
err = inst.CommitFields(db, set.NewSet("action"))
if err != nil {
return
}
return
}
func (p *Planner) checkInstance(db *database.Database,
deply *deployment.Deployment) (err error) {
if deply.State == deployment.Reserved {
return
}
inst, err := instance.Get(db, deply.Instance)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
inst = nil
err = nil
} else {
return
}
}
if inst == nil && deply.Kind == deployment.Instance {
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
}).Info("scheduler: Removing deployment for destroyed instance")
err = deployment.Remove(db, deply.Id)
if err != nil {
return
}
return
}
unt := p.unitsMap[deply.Unit]
if unt == nil {
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
}).Error("scheduler: Failed to find unit for deployment")
// err = deployment.Remove(db, deply.Id)
// if err != nil {
// return
// }
return
}
if inst == nil {
return
}
if deply.Action == deployment.Restore && inst.IsActive() {
deply.Action = ""
deply.State = deployment.Deployed
err = deply.CommitFields(db, set.NewSet("state", "action"))
if err != nil {
return
}
}
status := deployment.Unhealthy
if inst.Guest != nil {
if inst.Guest.Status == types.Running ||
inst.Guest.Status == types.ReloadingClean {
now := time.Now()
heartbeatTtl := time.Duration(
settings.System.InstanceTimestampTtl) * time.Second
if now.Sub(inst.Guest.Heartbeat) <= heartbeatTtl {
status = deployment.Healthy
} else if now.Sub(inst.Guest.Timestamp) > heartbeatTtl {
status = deployment.Unknown
}
}
}
if deply.Status != status {
deply.Status = status
err = deply.CommitFields(db, set.NewSet("status"))
if err != nil {
return
}
}
if deply.Action != "" {
return
}
switch deply.State {
case deployment.Archived:
return
}
if deply.State == deployment.Deployed && !unt.HasDeployment(deply.Id) {
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
}).Info("scheduler: Restoring deployment")
err = unt.RestoreDeployment(db, deply.Id)
if err != nil {
return
}
}
spc, err := spec.Get(db, deply.Spec)
if err != nil {
return
}
if spc.Instance == nil {
return
}
if deply.State != deployment.Deployed {
return
}
pln, err := plan.Get(db, spc.Instance.Plan)
if pln == nil {
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
}).Info("scheduler: Failed to find plan for deployment")
return
}
data, err := buildEvalData(unt, inst)
if err != nil {
return
}
var statement *plan.Statement
action := ""
threshold := 0
for _, statement = range pln.Statements {
action, threshold, err = eval.Eval(data, statement.Statement)
if err != nil {
return
}
threshold = utils.Max(deployment.ThresholdMin, threshold)
action, err = deply.HandleStatement(
db, statement.Id, threshold, action)
if err != nil {
return
}
if action != "" {
break
}
}
if action != "" {
switch action {
case plan.Start:
err = p.setInstanceAction(db, deply, inst,
statement, threshold, instance.Start)
if err != nil {
return
}
break
case plan.Stop:
err = p.setInstanceAction(db, deply, inst,
statement, threshold, instance.Stop)
if err != nil {
return
}
break
case plan.Restart:
err = p.setInstanceAction(db, deply, inst,
statement, threshold, instance.Restart)
if err != nil {
return
}
break
case plan.Destroy:
err = p.setInstanceAction(db, deply, inst,
statement, threshold, instance.Destroy)
if err != nil {
return
}
break
default:
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
"statement": statement.Statement,
"threshold": threshold,
"action": action,
}).Error("scheduler: Unknown plan action")
}
}
return
}
func (p *Planner) ApplyPlans(db *database.Database) (err error) {
deployments, err := deployment.GetAll(db, &bson.M{})
if err != nil {
return
}
p.unitsMap, err = unit.GetAllMap(db, &bson.M{})
if err != nil {
return
}
var waiters sync.WaitGroup
batch := make(chan struct{}, settings.System.PlannerBatchSize)
for _, deply := range deployments {
waiters.Add(1)
batch <- struct{}{}
go func(deply *deployment.Deployment) {
defer func() {
<-batch
waiters.Done()
}()
switch deply.Kind {
case deployment.Instance, deployment.Image:
e := p.checkInstance(db, deply)
if e != nil {
logrus.WithFields(logrus.Fields{
"deployment": deply.Id.Hex(),
"instance": deply.Instance.Hex(),
"pod": deply.Pod.Hex(),
"unit": deply.Unit.Hex(),
"error": e,
}).Error("scheduler: Failed to check instance deployment")
}
break
}
if deply.State == deployment.Reserved &&
deply.Action == deployment.Destroy &&
time.Since(deply.Timestamp) > 300*time.Second {
err := deployment.Remove(db, deply.Id)
if err != nil {
logrus.WithFields(logrus.Fields{
"deployment_id": deply.Id.Hex(),
"error": err,
}).Error("deploy: Failed to remove deployment")
return
}
event.PublishDispatch(db, "pod.change")
}
}(deply)
}
waiters.Wait()
return
}
================================================
FILE: planner/utils.go
================================================
package planner
import (
"time"
"github.com/pritunl/pritunl-cloud/eval"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/plan"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
)
func buildEvalData(unt *unit.Unit,
inst *instance.Instance) (data eval.Data, err error) {
lastTimestamp := 0
lastHeartbeat := 0
if inst.IsActive() {
now := time.Now()
uptime := int(now.Sub(inst.Timestamp).Seconds())
if inst.Guest != nil {
lastTimestamp = int(now.Sub(inst.Guest.Timestamp).Seconds())
lastHeartbeat = int(now.Sub(inst.Guest.Heartbeat).Seconds())
}
lastTimestamp = utils.Min(lastTimestamp, uptime)
lastHeartbeat = utils.Min(lastHeartbeat, uptime)
}
dataStrct := plan.Data{
Unit: plan.Unit{
Name: unt.Name,
Count: unt.Count,
},
Instance: plan.Instance{
Name: inst.Name,
State: inst.State,
Action: inst.Action,
LastTimestamp: lastTimestamp,
LastHeartbeat: lastHeartbeat,
},
}
data, err = dataStrct.Export()
if err != nil {
return
}
return
}
================================================
FILE: pod/pod.go
================================================
package pod
import (
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
)
type Pod struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"`
UserDrafts map[bson.ObjectID][]*UnitDraft `bson:"drafts" json:"-"`
Drafts []*UnitDraft `bson:"-" json:"drafts"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
}
type UnitDraft struct {
Id bson.ObjectID `bson:"id" json:"id"`
Name string `bson:"name" json:"name"`
Spec string `bson:"spec" json:"spec"`
Delete bool `bson:"delete" json:"delete"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
New bool `bson:"new" json:"new"`
}
func (p *Pod) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
p.Name = utils.FilterName(p.Name)
if p.Organization.IsZero() {
errData = &errortypes.ErrorData{
Error: "missing_organization",
Message: "Missing organization",
}
return
}
if p.UserDrafts == nil {
p.UserDrafts = map[bson.ObjectID][]*UnitDraft{}
}
return
}
func (p *Pod) Json(usrId bson.ObjectID) {
if p.UserDrafts != nil && p.UserDrafts[usrId] != nil {
p.Drafts = p.UserDrafts[usrId]
} else {
p.Drafts = []*UnitDraft{}
}
}
func (p *Pod) InitUnits(db *database.Database, units []*unit.UnitInput) (
errData *errortypes.ErrorData, err error) {
newUnits := []*unit.Unit{}
newSpecs := []*spec.Spec{}
updateSpecs := []*spec.Spec{}
for _, unitData := range units {
if unitData.Delete {
continue
}
unt := &unit.Unit{
Id: bson.NewObjectID(),
Pod: p.Id,
Organization: p.Organization,
Name: unitData.Name,
Spec: unitData.Spec,
SpecIndex: 1,
Deployments: []bson.ObjectID{},
}
newSpec, updateSpec, ed, e := unt.Parse(db, true)
if e != nil {
err = e
return
}
if ed != nil {
errData = ed
return
}
newUnits = append(newUnits, unt)
if newSpec != nil {
newSpecs = append(newSpecs, newSpec)
}
if updateSpec != nil {
updateSpecs = append(updateSpecs, updateSpec)
}
}
for _, unt := range newUnits {
err = unt.Insert(db)
if err != nil {
return
}
}
for _, spc := range newSpecs {
err = spc.Insert(db)
if err != nil {
return
}
}
for _, spc := range updateSpecs {
err = spc.CommitData(db)
if err != nil {
return
}
}
return
}
func (p *Pod) CommitFieldsUnits(db *database.Database,
units []*unit.UnitInput, fields set.Set) (
errData *errortypes.ErrorData, err error) {
curUnitsMap, err := unit.GetAllMap(db, &bson.M{
"pod": p.Id,
})
if err != nil {
return
}
unitsName := set.NewSet()
parsedUnits := []*unit.Unit{}
parsedUnitsNew := []*unit.Unit{}
parsedUnitsDel := []*unit.Unit{}
newSpecs := []*spec.Spec{}
updateSpecs := []*spec.Spec{}
for _, unitData := range units {
curUnit := curUnitsMap[unitData.Id]
if unitData.Delete {
if curUnit == nil {
continue
}
parsedUnitsDel = append(parsedUnitsDel, curUnit)
} else if curUnit == nil {
curUnit := curUnitsMap[unitData.Id]
if curUnit != nil {
continue
}
unt := &unit.Unit{
Id: bson.NewObjectID(),
Pod: p.Id,
Organization: p.Organization,
Name: unitData.Name,
Spec: unitData.Spec,
SpecIndex: 1,
Deployments: []bson.ObjectID{},
}
newSpec, updateSpec, ed, e := unt.Parse(db, true)
if e != nil {
err = e
return
}
if ed != nil {
errData = ed
return
}
if newSpec != nil {
newSpecs = append(newSpecs, newSpec)
}
if updateSpec != nil {
updateSpecs = append(updateSpecs, updateSpec)
}
if unitsName.Contains(unt.Name) {
errData = &errortypes.ErrorData{
Error: "unit_duplicate_name",
Message: "Duplicate unit name",
}
return
}
unitsName.Add(unt.Name)
parsedUnitsNew = append(parsedUnitsNew, unt)
} else {
curUnit.Name = unitData.Name
curUnit.Spec = unitData.Spec
if !unitData.DeploySpec.IsZero() {
deploySpec, e := spec.Get(db, unitData.DeploySpec)
if e != nil || deploySpec.Unit != curUnit.Id {
errData = &errortypes.ErrorData{
Error: "unit_deploy_spec_invalid",
Message: "Invalid unit deployment commit",
}
return
}
curUnit.DeploySpec = deploySpec.Id
}
newSpec, updateSpec, ed, e := curUnit.Parse(db, false)
if e != nil {
err = e
return
}
if ed != nil {
errData = ed
return
}
if newSpec != nil {
newSpecs = append(newSpecs, newSpec)
}
if updateSpec != nil {
updateSpecs = append(updateSpecs, updateSpec)
}
if unitsName.Contains(curUnit.Name) {
errData = &errortypes.ErrorData{
Error: "unit_duplicate_name",
Message: "Duplicate unit name",
}
return
}
unitsName.Add(curUnit.Name)
parsedUnits = append(parsedUnits, curUnit)
}
}
for _, unt := range parsedUnitsDel {
deplys, e := deployment.GetAll(db, &bson.M{
"pod": p.Id,
"unit": unt.Id,
"organization": p.Organization,
})
if e != nil {
err = e
return
}
if len(deplys) > 0 {
errData = &errortypes.ErrorData{
Error: "unit_delete_active_deployments",
Message: "Cannot delete unit with active deployments",
}
return
}
err = unit.RemoveOrg(db, p.Organization, unt.Id)
if err != nil {
return
}
}
for _, unt := range parsedUnits {
err = unt.CommitFields(db, set.NewSet(
"name",
"kind",
"count",
"spec",
"last_spec",
"deploy_spec",
"hash",
))
if err != nil {
return
}
}
for _, unt := range parsedUnitsNew {
err = unt.Insert(db)
if err != nil {
return
}
}
for _, spc := range newSpecs {
err = spc.Insert(db)
if err != nil {
return
}
}
for _, spc := range updateSpecs {
err = spc.CommitData(db)
if err != nil {
return
}
}
err = p.CommitFields(db, fields)
if err != nil {
return
}
return
}
func (p *Pod) Commit(db *database.Database) (err error) {
coll := db.Pods()
err = coll.Commit(p.Id, p)
if err != nil {
return
}
return
}
func (p *Pod) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Pods()
err = coll.CommitFields(p.Id, p, fields)
if err != nil {
return
}
return
}
func (p *Pod) Insert(db *database.Database) (err error) {
coll := db.Pods()
_, err = coll.InsertOne(db, p)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: pod/utils.go
================================================
package pod
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, podId bson.ObjectID) (
pd *Pod, err error) {
coll := db.Pods()
pd = &Pod{}
err = coll.FindOneId(podId, pd)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, pdId bson.ObjectID) (
pd *Pod, err error) {
coll := db.Pods()
pd = &Pod{}
err = coll.FindOne(db, &bson.M{
"_id": pdId,
"organization": orgId,
}).Decode(pd)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (pd *Pod, err error) {
coll := db.Pods()
pd = &Pod{}
err = coll.FindOne(db, query).Decode(pd)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
pods []*Pod, err error) {
coll := db.Pods()
pods = []*Pod{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
pd := &Pod{}
err = cursor.Decode(pd)
if err != nil {
err = database.ParseError(err)
return
}
pods = append(pods, pd)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (pods []*Pod, count int64, err error) {
coll := db.Pods()
pods = []*Pod{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
pd := &Pod{}
err = cursor.Decode(pd)
if err != nil {
err = database.ParseError(err)
return
}
pods = append(pods, pd)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func UpdateDrafts(db *database.Database, podId, usrId bson.ObjectID,
drafts []*UnitDraft) (err error) {
for _, draft := range drafts {
draft.Timestamp = time.Now()
}
coll := db.Pods()
_, err = coll.UpdateOne(db, &bson.M{
"_id": podId,
}, &bson.M{
"$set": &bson.M{
"drafts." + usrId.Hex(): drafts,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return nil
}
func UpdateDraftsOrg(db *database.Database, orgId, podId, usrId bson.ObjectID,
drafts []*UnitDraft) (err error) {
for _, draft := range drafts {
draft.Timestamp = time.Now()
}
coll := db.Pods()
_, err = coll.UpdateOne(db, &bson.M{
"_id": podId,
"organization": orgId,
}, &bson.M{
"$set": &bson.M{
"drafts." + usrId.Hex(): drafts,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return nil
}
func Remove(db *database.Database, podId bson.ObjectID) (err error) {
coll := db.Pods()
err = spec.RemoveAll(db, &bson.M{
"pod": podId,
})
if err != nil {
return
}
err = unit.RemoveAll(db, &bson.M{
"pod": podId,
})
if err != nil {
return
}
_, err = coll.DeleteOne(db, &bson.M{
"_id": podId,
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveOrg(db *database.Database, orgId, podId bson.ObjectID) (
err error) {
coll := db.Pods()
err = spec.RemoveAll(db, &bson.M{
"pod": podId,
"organization": orgId,
})
if err != nil {
return
}
err = unit.RemoveAll(db, &bson.M{
"pod": podId,
"organization": orgId,
})
if err != nil {
return
}
_, err = coll.DeleteOne(db, &bson.M{
"_id": podId,
"organization": orgId,
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, podIds []bson.ObjectID) (
err error) {
coll := db.Pods()
err = spec.RemoveAll(db, &bson.M{
"pod": &bson.M{
"$in": podIds,
},
})
if err != nil {
return
}
err = unit.RemoveAll(db, &bson.M{
"pod": &bson.M{
"$in": podIds,
},
})
if err != nil {
return
}
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": podIds,
},
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
podIds []bson.ObjectID) (err error) {
coll := db.Pods()
err = spec.RemoveAll(db, &bson.M{
"pod": &bson.M{
"$in": podIds,
},
"organization": orgId,
})
if err != nil {
return
}
err = unit.RemoveAll(db, &bson.M{
"pod": &bson.M{
"$in": podIds,
},
"organization": orgId,
})
if err != nil {
return
}
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": podIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: policy/constants.go
================================================
package policy
const (
Optional = "optional"
Required = "required"
Disabled = "disabled"
OperatingSystem = "operating_system"
Browser = "browser"
Location = "location"
WhitelistNetworks = "whitelist_networks"
BlacklistNetworks = "blacklist_networks"
)
================================================
FILE: policy/policy.go
================================================
package policy
import (
"fmt"
"net"
"net/http"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/subscription"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/useragent"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Rule struct {
Type string `bson:"type" json:"type"`
Disable bool `bson:"disable" json:"disable"`
Values []string `bson:"values" json:"values"`
}
type Policy struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Disabled bool `bson:"disabled" json:"disabled"`
Roles []string `bson:"roles" json:"roles"`
Rules map[string]*Rule `bson:"rules" json:"rules"`
AdminSecondary bson.ObjectID `bson:"admin_secondary,omitempty" json:"admin_secondary"`
UserSecondary bson.ObjectID `bson:"user_secondary,omitempty" json:"user_secondary"`
AdminDeviceSecondary bool `bson:"admin_device_secondary" json:"admin_device_secondary"`
UserDeviceSecondary bool `bson:"user_device_secondary" json:"user_device_secondary"`
}
func (p *Policy) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
p.Name = utils.FilterName(p.Name)
if p.Roles == nil {
p.Roles = []string{}
}
if p.Rules == nil {
p.Rules = map[string]*Rule{}
}
for _, rule := range p.Rules {
switch rule.Type {
case OperatingSystem:
break
case Browser:
break
case Location:
if !subscription.Sub.Active {
errData = &errortypes.ErrorData{
Error: "location_subscription_required",
Message: "Location policy requires subscription " +
"for GeoIP service.",
}
return
}
break
case WhitelistNetworks:
break
case BlacklistNetworks:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_rule_type",
Message: "Rule type is invalid",
}
return
}
}
if !p.AdminSecondary.IsZero() &&
settings.Auth.GetSecondaryProvider(p.AdminSecondary) == nil {
p.AdminSecondary = bson.NilObjectID
}
if !p.UserSecondary.IsZero() &&
settings.Auth.GetSecondaryProvider(p.UserSecondary) == nil {
p.UserSecondary = bson.NilObjectID
}
hasWebAuthn := false
nodes, err := node.GetAll(db)
if err != nil {
return
}
for _, nde := range nodes {
if nde.WebauthnDomain != "" {
hasWebAuthn = true
break
}
}
if (p.AdminDeviceSecondary || p.UserDeviceSecondary) && !hasWebAuthn {
errData = &errortypes.ErrorData{
Error: "webauthn_domain_unavailable",
Message: "At least one node must have a WebAuthn domain " +
"configured to use WebAuthn device authentication",
}
return
}
return
}
func (p *Policy) ValidateUser(db *database.Database, usr *user.User,
r *http.Request) (errData *errortypes.ErrorData, err error) {
if p.Disabled {
return
}
agnt, err := useragent.Parse(db, r)
if err != nil {
return
}
for _, rule := range p.Rules {
switch rule.Type {
case OperatingSystem:
match := false
for _, value := range rule.Values {
if value == agnt.OperatingSystem {
match = true
break
}
}
if !match {
if rule.Disable {
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
return
}
} else {
errData = &errortypes.ErrorData{
Error: "operating_system_policy",
Message: "Operating system not permitted",
}
}
return
}
break
case Browser:
match := false
for _, value := range rule.Values {
if value == agnt.Browser {
match = true
break
}
}
if !match {
if rule.Disable {
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
return
}
} else {
errData = &errortypes.ErrorData{
Error: "browser_policy",
Message: "Browser not permitted",
}
}
return
}
break
case Location:
match := false
regionKey := fmt.Sprintf("%s_%s",
agnt.CountryCode, agnt.RegionCode)
for _, value := range rule.Values {
if value == agnt.CountryCode || value == regionKey {
match = true
break
}
}
if !match {
if rule.Disable {
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
return
}
} else {
errData = &errortypes.ErrorData{
Error: "location_policy",
Message: "Location not permitted",
}
}
return
}
break
case WhitelistNetworks:
match := false
clientIp := net.ParseIP(agnt.Ip)
for _, value := range rule.Values {
_, network, e := net.ParseCIDR(value)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "policy: Failed to parse network"),
}
logrus.WithFields(logrus.Fields{
"network": value,
"error": err,
}).Error("policy: Invalid whitelist network")
err = nil
continue
}
if network.Contains(clientIp) {
match = true
break
}
}
if !match {
if rule.Disable {
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
return
}
} else {
errData = &errortypes.ErrorData{
Error: "whitelist_networks_policy",
Message: "Network not permitted",
}
}
return
}
break
case BlacklistNetworks:
match := false
clientIp := net.ParseIP(agnt.Ip)
for _, value := range rule.Values {
_, network, e := net.ParseCIDR(value)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "policy: Failed to parse network"),
}
logrus.WithFields(logrus.Fields{
"network": value,
"error": err,
}).Error("policy: Invalid blacklist network")
err = nil
continue
}
if network.Contains(clientIp) {
match = true
break
}
}
if match {
if rule.Disable {
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
return
}
} else {
errData = &errortypes.ErrorData{
Error: "blacklist_networks_policy",
Message: "Network not permitted",
}
}
return
}
break
}
}
return
}
func (p *Policy) Commit(db *database.Database) (err error) {
coll := db.Policies()
err = coll.Commit(p.Id, p)
if err != nil {
return
}
return
}
func (p *Policy) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Policies()
err = coll.CommitFields(p.Id, p, fields)
if err != nil {
return
}
return
}
func (p *Policy) Insert(db *database.Database) (err error) {
coll := db.Policies()
if !p.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("policy: Policy already exists"),
}
return
}
_, err = coll.InsertOne(db, p)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: policy/utils.go
================================================
package policy
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, policyId bson.ObjectID) (
polcy *Policy, err error) {
coll := db.Policies()
polcy = &Policy{}
err = coll.FindOneId(policyId, polcy)
if err != nil {
return
}
return
}
func GetService(db *database.Database, podId bson.ObjectID) (
policies []*Policy, err error) {
coll := db.Policies()
policies = []*Policy{}
cursor, err := coll.Find(
db,
&bson.M{
"pods": podId,
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
polcy := &Policy{}
err = cursor.Decode(polcy)
if err != nil {
err = database.ParseError(err)
return
}
policies = append(policies, polcy)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetRoles(db *database.Database, roles []string) (
policies []*Policy, err error) {
coll := db.Policies()
policies = []*Policy{}
if roles == nil {
roles = []string{}
}
cursor, err := coll.Find(
db,
&bson.M{
"roles": &bson.M{
"$in": roles,
},
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
polcy := &Policy{}
err = cursor.Decode(polcy)
if err != nil {
err = database.ParseError(err)
return
}
policies = append(policies, polcy)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database) (policies []*Policy, err error) {
coll := db.Policies()
policies = []*Policy{}
cursor, err := coll.Find(
db,
&bson.M{},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
polcy := &Policy{}
err = cursor.Decode(polcy)
if err != nil {
err = database.ParseError(err)
return
}
policies = append(policies, polcy)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (polcies []*Policy, count int64, err error) {
coll := db.Policies()
polcies = []*Policy{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
polcy := &Policy{}
err = cursor.Decode(polcy)
if err != nil {
err = database.ParseError(err)
return
}
polcies = append(polcies, polcy)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, policyId bson.ObjectID) (err error) {
coll := db.Policies()
_, err = coll.DeleteMany(db, &bson.M{
"_id": policyId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMulti(db *database.Database, polcyIds []bson.ObjectID) (
err error) {
coll := db.Policies()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": polcyIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: pool/constants.go
================================================
package pool
const (
Lvm = "lvm"
Active = "active"
)
================================================
FILE: pool/pool.go
================================================
package pool
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Pool struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
Zone bson.ObjectID `bson:"zone" json:"zone"`
Type string `bson:"type" json:"type"`
VgName string `bson:"vg_name" json:"vg_name"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Zone bson.ObjectID `bson:"zone" json:"zone"`
}
func (p *Pool) Json(nodeNames map[bson.ObjectID]string) {
}
func (p *Pool) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
p.Name = utils.FilterName(p.Name)
if p.Datacenter.IsZero() {
errData = &errortypes.ErrorData{
Error: "invalid_datacenter",
Message: "Missing required datacenter",
}
return
}
if p.Zone.IsZero() {
errData = &errortypes.ErrorData{
Error: "invalid_zone",
Message: "Missing required zone",
}
return
}
return
}
func (p *Pool) Commit(db *database.Database) (err error) {
coll := db.Pools()
err = coll.Commit(p.Id, p)
if err != nil {
return
}
return
}
func (p *Pool) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Pools()
err = coll.CommitFields(p.Id, p, fields)
if err != nil {
return
}
return
}
func (p *Pool) Insert(db *database.Database) (err error) {
coll := db.Pools()
_, err = coll.InsertOne(db, p)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: pool/utils.go
================================================
package pool
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, poolId bson.ObjectID) (
pl *Pool, err error) {
coll := db.Pools()
pl = &Pool{}
err = coll.FindOneId(poolId, pl)
if err != nil {
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (pl *Pool, err error) {
coll := db.Pools()
pl = &Pool{}
err = coll.FindOne(db, query).Decode(pl)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
pools []*Pool, err error) {
coll := db.Pools()
pools = []*Pool{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Pool{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
pools = append(pools, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (pools []*Pool, count int64, err error) {
coll := db.Pools()
pools = []*Pool{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
pl := &Pool{}
err = cursor.Decode(pl)
if err != nil {
err = database.ParseError(err)
return
}
pools = append(pools, pl)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNames(db *database.Database, query *bson.M) (
pools []*Pool, err error) {
coll := db.Pools()
pools = []*Pool{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetProjection(bson.D{
{"_id", 1},
{"name", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
pl := &Pool{}
err = cursor.Decode(pl)
if err != nil {
err = database.ParseError(err)
return
}
pools = append(pools, pl)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, poolId bson.ObjectID) (err error) {
coll := db.Pools()
_, err = coll.DeleteOne(db, &bson.M{
"_id": poolId,
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, poolIds []bson.ObjectID) (
err error) {
coll := db.Pools()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": poolIds,
},
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: proxy/constants.go
================================================
package proxy
const (
Online = 5
UnknownHigh = 4
UnknownMid = 3
UnknownLow = 2
Offline = 1
)
================================================
FILE: proxy/domain.go
================================================
package proxy
import (
"crypto/md5"
"crypto/tls"
"math/rand"
"net/http"
"strconv"
"sync"
"sync/atomic"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/balancer"
)
type Domain struct {
Hash []byte
Requests *int32
RequestsPrev [5]int
RequestsTotal int
Retries *int32
RetriesPrev [5]int
RetriesTotal int
Lock sync.Mutex
ProxyProto string
ProxyPort int
SkipVerify bool
Balancer *balancer.Balancer
Domain *balancer.Domain
ClientAuthority *authority.Authority
ClientCertificate *tls.Certificate
OnlineWebFirst []*Handler
UnknownHighWebFirst []*Handler
UnknownMidWebFirst []*Handler
UnknownLowWebFirst []*Handler
OfflineWebFirst []*Handler
OnlineWebSecond []*Handler
UnknownHighWebSecond []*Handler
UnknownMidWebSecond []*Handler
UnknownLowWebSecond []*Handler
OfflineWebSecond []*Handler
OnlineWebThird []*Handler
UnknownHighWebThird []*Handler
UnknownMidWebThird []*Handler
UnknownLowWebThird []*Handler
OfflineWebThird []*Handler
WebSocketConns set.Set
WebSocketConnsLock sync.Mutex
}
func (d *Domain) CalculateHash() {
h := md5.New()
h.Write([]byte(d.ProxyProto))
h.Write([]byte(strconv.Itoa(d.ProxyPort)))
h.Write([]byte(strconv.FormatBool(d.SkipVerify)))
h.Write([]byte(d.Balancer.Id.Hex()))
h.Write([]byte(d.Balancer.Name))
h.Write([]byte(d.Balancer.CheckPath))
h.Write([]byte(strconv.FormatBool(d.Balancer.WebSockets)))
h.Write([]byte(d.Domain.Domain))
h.Write([]byte(d.Domain.Host))
if !d.Balancer.ClientAuthority.IsZero() {
h.Write([]byte(d.Balancer.ClientAuthority.Hex()))
}
for _, backend := range d.Balancer.Backends {
h.Write([]byte(backend.Protocol))
h.Write([]byte(backend.Hostname))
h.Write([]byte(strconv.Itoa(backend.Port)))
}
d.Hash = h.Sum(nil)
}
func (d *Domain) Init() {
d.Lock.Lock()
defer d.Lock.Unlock()
if !d.Balancer.ClientAuthority.IsZero() {
//clientAuthr, err := authority.Get(db, d.Balancer.ClientAuthority)
//if err != nil {
// if _, ok := err.(*database.NotFoundError); ok {
// err = nil
//
// logrus.WithFields(logrus.Fields{
// "balancer_id": d.Balancer.Id.Hex(),
// "client_authority_id": d.Balancer.ClientAuthority.Hex(),
// }).Warn("proxy: Service client authority not found")
// } else {
// return
// }
//}
//
// var cert *tls.Certificate
//if clientAuthr != nil {
// cert, err = clientAuthr.CreateClientCertificate(db)
// if err != nil {
// return
// }
//}
}
unknownHighWebFirst := []*Handler{}
unknownHighWebSecond := []*Handler{}
unknownHighWebThird := []*Handler{}
for i, backend := range d.Balancer.Backends {
hand := NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,
backend, d.ResponseHandler, d.ErrorHandlerFirst)
unknownHighWebFirst = append(unknownHighWebFirst, hand)
hand = NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,
backend, d.ResponseHandler, d.ErrorHandlerSecond)
unknownHighWebSecond = append(unknownHighWebSecond, hand)
hand = NewHandler(i, UnknownHigh, d.ProxyProto, d.ProxyPort, d,
backend, d.ResponseHandler, d.ErrorHandlerThird)
unknownHighWebThird = append(unknownHighWebThird, hand)
}
d.OnlineWebFirst = []*Handler{}
d.UnknownHighWebFirst = unknownHighWebFirst
d.UnknownMidWebFirst = []*Handler{}
d.UnknownLowWebFirst = []*Handler{}
d.OfflineWebFirst = []*Handler{}
d.OnlineWebSecond = []*Handler{}
d.UnknownHighWebSecond = unknownHighWebSecond
d.UnknownMidWebSecond = []*Handler{}
d.UnknownLowWebSecond = []*Handler{}
d.OfflineWebSecond = []*Handler{}
d.OnlineWebThird = []*Handler{}
d.UnknownHighWebThird = unknownHighWebThird
d.UnknownMidWebThird = []*Handler{}
d.UnknownLowWebThird = []*Handler{}
d.OfflineWebThird = []*Handler{}
d.WebSocketConns = set.NewSet()
}
func (d *Domain) ServeHTTPFirst(rw http.ResponseWriter, r *http.Request) {
atomic.AddInt32(d.Requests, 1)
onlineWebFirst := d.OnlineWebFirst
l := len(onlineWebFirst)
if l != 0 {
onlineWebFirst[rand.Intn(l)].Serve(rw, r)
return
}
unknownHighWebFirst := d.UnknownHighWebFirst
l = len(unknownHighWebFirst)
if l != 0 {
unknownHighWebFirst[rand.Intn(l)].Serve(rw, r)
return
}
unknownMidWebFirst := d.UnknownMidWebFirst
l = len(unknownMidWebFirst)
if l != 0 {
unknownMidWebFirst[rand.Intn(l)].Serve(rw, r)
return
}
unknownLowWebFirst := d.UnknownLowWebFirst
l = len(unknownLowWebFirst)
if l != 0 {
unknownLowWebFirst[rand.Intn(l)].Serve(rw, r)
return
}
offlineWebFirst := d.OfflineWebFirst
l = len(offlineWebFirst)
if l != 0 {
offlineWebFirst[rand.Intn(l)].Serve(rw, r)
return
}
rw.WriteHeader(http.StatusBadGateway)
}
func (d *Domain) ServeHTTPSecond(rw http.ResponseWriter, r *http.Request) {
atomic.AddInt32(d.Retries, 1)
onlineWebSecond := d.OnlineWebSecond
l := len(onlineWebSecond)
if l != 0 {
onlineWebSecond[rand.Intn(l)].Serve(rw, r)
return
}
unknownHighWebSecond := d.UnknownHighWebSecond
l = len(unknownHighWebSecond)
if l != 0 {
unknownHighWebSecond[rand.Intn(l)].Serve(rw, r)
return
}
unknownMidWebSecond := d.UnknownMidWebSecond
l = len(unknownMidWebSecond)
if l != 0 {
unknownMidWebSecond[rand.Intn(l)].Serve(rw, r)
return
}
unknownLowWebSecond := d.UnknownLowWebSecond
l = len(unknownLowWebSecond)
if l != 0 {
unknownLowWebSecond[rand.Intn(l)].Serve(rw, r)
return
}
offlineWebSecond := d.OfflineWebSecond
l = len(offlineWebSecond)
if l != 0 {
offlineWebSecond[rand.Intn(l)].Serve(rw, r)
return
}
rw.WriteHeader(http.StatusBadGateway)
}
func (d *Domain) ServeHTTPThird(rw http.ResponseWriter, r *http.Request) {
atomic.AddInt32(d.Retries, 1)
onlineWebThird := d.OnlineWebThird
l := len(onlineWebThird)
if l != 0 {
onlineWebThird[rand.Intn(l)].Serve(rw, r)
return
}
unknownHighWebThird := d.UnknownHighWebThird
l = len(unknownHighWebThird)
if l != 0 {
unknownHighWebThird[rand.Intn(l)].Serve(rw, r)
return
}
unknownMidWebThird := d.UnknownMidWebThird
l = len(unknownMidWebThird)
if l != 0 {
unknownMidWebThird[rand.Intn(l)].Serve(rw, r)
return
}
unknownLowWebThird := d.UnknownLowWebThird
l = len(unknownLowWebThird)
if l != 0 {
unknownLowWebThird[rand.Intn(l)].Serve(rw, r)
return
}
offlineWebThird := d.OfflineWebThird
l = len(offlineWebThird)
if l != 0 {
offlineWebThird[rand.Intn(l)].Serve(rw, r)
return
}
rw.WriteHeader(http.StatusBadGateway)
}
func (d *Domain) checkHandler(hand *Handler) {
resp, err := hand.CheckClient.Get(hand.CheckUrl)
if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {
if hand.State != Offline {
d.offlineHandler(hand)
}
} else {
if hand.State != Online {
d.upgradeHandler(hand)
}
}
}
func (d *Domain) Check() {
d.Lock.Lock()
defer d.Lock.Unlock()
for _, hand := range d.OnlineWebFirst {
go d.checkHandler(hand)
}
for _, hand := range d.UnknownHighWebFirst {
go d.checkHandler(hand)
}
for _, hand := range d.UnknownMidWebFirst {
go d.checkHandler(hand)
}
for _, hand := range d.UnknownLowWebFirst {
go d.checkHandler(hand)
}
for _, hand := range d.OfflineWebFirst {
go d.checkHandler(hand)
}
return
}
func (d *Domain) upgradeHandler(hand *Handler) {
d.Lock.Lock()
defer d.Lock.Unlock()
index := hand.Index
state := hand.State
switch state {
case Online:
break
case UnknownHigh:
if time.Since(hand.LastOnlineState) > 5*time.Second {
hand = d.UnknownHighWebFirst[index]
d.UnknownHighWebFirst[index] =
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil
d.UnknownHighWebFirst =
d.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]
for i, h := range d.UnknownHighWebFirst {
h.Index = i
}
hand.Index = len(d.OnlineWebFirst)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebFirst = append(d.OnlineWebFirst, hand)
hand = d.UnknownHighWebSecond[index]
d.UnknownHighWebSecond[index] =
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil
d.UnknownHighWebSecond =
d.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]
for i, h := range d.UnknownHighWebSecond {
h.Index = i
}
hand.Index = len(d.OnlineWebSecond)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebSecond = append(d.OnlineWebSecond, hand)
hand = d.UnknownHighWebThird[index]
d.UnknownHighWebThird[index] =
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil
d.UnknownHighWebThird =
d.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]
for i, h := range d.UnknownHighWebThird {
h.Index = i
}
hand.Index = len(d.OnlineWebThird)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebThird = append(d.OnlineWebThird, hand)
}
break
case UnknownMid:
if time.Since(hand.LastOnlineState) > 5*time.Second {
hand = d.UnknownMidWebFirst[index]
d.UnknownMidWebFirst[index] =
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil
d.UnknownMidWebFirst =
d.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]
for i, h := range d.UnknownMidWebFirst {
h.Index = i
}
hand.Index = len(d.OnlineWebFirst)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebFirst = append(d.OnlineWebFirst, hand)
hand = d.UnknownMidWebSecond[index]
d.UnknownMidWebSecond[index] =
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil
d.UnknownMidWebSecond =
d.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]
for i, h := range d.UnknownMidWebSecond {
h.Index = i
}
hand.Index = len(d.OnlineWebSecond)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebSecond = append(d.OnlineWebSecond, hand)
hand = d.UnknownMidWebThird[index]
d.UnknownMidWebThird[index] =
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil
d.UnknownMidWebThird =
d.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]
for i, h := range d.UnknownMidWebThird {
h.Index = i
}
hand.Index = len(d.OnlineWebThird)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebThird = append(d.OnlineWebThird, hand)
}
break
case UnknownLow:
if time.Since(hand.LastOnlineState) > 5*time.Second {
hand = d.UnknownLowWebFirst[index]
d.UnknownLowWebFirst[index] =
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil
d.UnknownLowWebFirst =
d.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]
for i, h := range d.UnknownLowWebFirst {
h.Index = i
}
hand.Index = len(d.OnlineWebFirst)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebFirst = append(d.OnlineWebFirst, hand)
hand = d.UnknownLowWebSecond[index]
d.UnknownLowWebSecond[index] =
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil
d.UnknownLowWebSecond =
d.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]
for i, h := range d.UnknownLowWebSecond {
h.Index = i
}
hand.Index = len(d.OnlineWebSecond)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebSecond = append(d.OnlineWebSecond, hand)
hand = d.UnknownLowWebThird[index]
d.UnknownLowWebThird[index] =
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil
d.UnknownLowWebThird =
d.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]
for i, h := range d.UnknownLowWebThird {
h.Index = i
}
hand.Index = len(d.OnlineWebThird)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebThird = append(d.OnlineWebThird, hand)
}
break
case Offline:
if time.Since(hand.LastOnlineState) > 5*time.Second {
hand = d.OfflineWebFirst[index]
d.OfflineWebFirst[index] =
d.OfflineWebFirst[len(d.OfflineWebFirst)-1]
d.OfflineWebFirst[len(d.OfflineWebFirst)-1] = nil
d.OfflineWebFirst =
d.OfflineWebFirst[:len(d.OfflineWebFirst)-1]
for i, h := range d.OfflineWebFirst {
h.Index = i
}
hand.Index = len(d.OnlineWebFirst)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebFirst = append(d.OnlineWebFirst, hand)
hand = d.OfflineWebSecond[index]
d.OfflineWebSecond[index] =
d.OfflineWebSecond[len(d.OfflineWebSecond)-1]
d.OfflineWebSecond[len(d.OfflineWebSecond)-1] = nil
d.OfflineWebSecond =
d.OfflineWebSecond[:len(d.OfflineWebSecond)-1]
for i, h := range d.OfflineWebSecond {
h.Index = i
}
hand.Index = len(d.OnlineWebSecond)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebSecond = append(d.OnlineWebSecond, hand)
hand = d.OfflineWebThird[index]
d.OfflineWebThird[index] =
d.OfflineWebThird[len(d.OfflineWebThird)-1]
d.OfflineWebThird[len(d.OfflineWebThird)-1] = nil
d.OfflineWebThird =
d.OfflineWebThird[:len(d.OfflineWebThird)-1]
for i, h := range d.OfflineWebThird {
h.Index = i
}
hand.Index = len(d.OnlineWebThird)
hand.State = Online
hand.LastOnlineState = time.Now()
d.OnlineWebThird = append(d.OnlineWebThird, hand)
}
break
}
}
func (d *Domain) downgradeHandler(hand *Handler) {
d.Lock.Lock()
defer d.Lock.Unlock()
index := hand.Index
state := hand.State
switch state {
case Online:
hand = d.OnlineWebFirst[index]
d.OnlineWebFirst[index] = d.OnlineWebFirst[len(d.OnlineWebFirst)-1]
d.OnlineWebFirst[len(d.OnlineWebFirst)-1] = nil
d.OnlineWebFirst = d.OnlineWebFirst[:len(d.OnlineWebFirst)-1]
for i, h := range d.OnlineWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownMidWebFirst)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebFirst = append(d.UnknownMidWebFirst, hand)
hand = d.OnlineWebSecond[index]
d.OnlineWebSecond[index] = d.OnlineWebSecond[len(d.OnlineWebSecond)-1]
d.OnlineWebSecond[len(d.OnlineWebSecond)-1] = nil
d.OnlineWebSecond = d.OnlineWebSecond[:len(d.OnlineWebSecond)-1]
for i, h := range d.OnlineWebSecond {
h.Index = i
}
hand.Index = len(d.UnknownMidWebSecond)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebSecond = append(d.UnknownMidWebSecond, hand)
hand = d.OnlineWebThird[index]
d.OnlineWebThird[index] = d.OnlineWebThird[len(d.OnlineWebThird)-1]
d.OnlineWebThird[len(d.OnlineWebThird)-1] = nil
d.OnlineWebThird = d.OnlineWebThird[:len(d.OnlineWebThird)-1]
for i, h := range d.OnlineWebThird {
h.Index = i
}
hand.Index = len(d.UnknownMidWebThird)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebThird = append(d.UnknownMidWebThird, hand)
break
case UnknownHigh:
hand = d.UnknownHighWebFirst[index]
d.UnknownHighWebFirst[index] =
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil
d.UnknownHighWebFirst =
d.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]
for i, h := range d.UnknownHighWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownMidWebFirst)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebFirst = append(d.UnknownMidWebFirst, hand)
hand = d.UnknownHighWebSecond[index]
d.UnknownHighWebSecond[index] =
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil
d.UnknownHighWebSecond =
d.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]
for i, h := range d.UnknownHighWebSecond {
h.Index = i
}
hand.Index = len(d.UnknownMidWebSecond)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebSecond = append(d.UnknownMidWebSecond, hand)
hand = d.UnknownHighWebThird[index]
d.UnknownHighWebThird[index] =
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil
d.UnknownHighWebThird =
d.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]
for i, h := range d.UnknownHighWebThird {
h.Index = i
}
hand.Index = len(d.UnknownMidWebThird)
hand.State = UnknownMid
hand.LastState = time.Now()
d.UnknownMidWebThird = append(d.UnknownMidWebThird, hand)
break
case UnknownMid:
if time.Since(hand.LastState) > 1*time.Second {
hand = d.UnknownMidWebFirst[index]
d.UnknownMidWebFirst[index] =
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil
d.UnknownMidWebFirst =
d.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]
for i, h := range d.UnknownMidWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownLowWebFirst)
hand.State = UnknownLow
hand.LastState = time.Now()
d.UnknownLowWebFirst = append(d.UnknownLowWebFirst, hand)
hand = d.UnknownMidWebSecond[index]
d.UnknownMidWebSecond[index] =
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil
d.UnknownMidWebSecond =
d.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]
for i, h := range d.UnknownMidWebSecond {
h.Index = i
}
hand.Index = len(d.UnknownLowWebSecond)
hand.State = UnknownLow
hand.LastState = time.Now()
d.UnknownLowWebSecond = append(d.UnknownLowWebSecond, hand)
hand = d.UnknownMidWebThird[index]
d.UnknownMidWebThird[index] =
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil
d.UnknownMidWebThird =
d.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]
for i, h := range d.UnknownMidWebThird {
h.Index = i
}
hand.Index = len(d.UnknownLowWebThird)
hand.State = UnknownLow
hand.LastState = time.Now()
d.UnknownLowWebThird = append(d.UnknownLowWebThird, hand)
}
break
case UnknownLow:
if time.Since(hand.LastState) > 2*time.Second {
hand = d.UnknownLowWebFirst[index]
d.UnknownLowWebFirst[index] =
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil
d.UnknownLowWebFirst =
d.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]
for i, h := range d.UnknownLowWebFirst {
h.Index = i
}
hand.Index = len(d.OfflineWebFirst)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebFirst = append(d.OfflineWebFirst, hand)
hand = d.UnknownLowWebSecond[index]
d.UnknownLowWebSecond[index] =
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil
d.UnknownLowWebSecond =
d.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]
for i, h := range d.UnknownLowWebSecond {
h.Index = i
}
hand.Index = len(d.OfflineWebSecond)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebSecond = append(d.OfflineWebSecond, hand)
hand = d.UnknownLowWebThird[index]
d.UnknownLowWebThird[index] =
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil
d.UnknownLowWebThird =
d.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]
for i, h := range d.UnknownLowWebThird {
h.Index = i
}
hand.Index = len(d.OfflineWebThird)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebThird = append(d.OfflineWebThird, hand)
}
break
case Offline:
break
}
}
func (d *Domain) offlineHandler(hand *Handler) {
d.Lock.Lock()
defer d.Lock.Unlock()
index := hand.Index
state := hand.State
switch state {
case Online:
hand = d.OnlineWebFirst[index]
d.OnlineWebFirst[index] = d.OnlineWebFirst[len(d.OnlineWebFirst)-1]
d.OnlineWebFirst[len(d.OnlineWebFirst)-1] = nil
d.OnlineWebFirst = d.OnlineWebFirst[:len(d.OnlineWebFirst)-1]
for i, h := range d.OnlineWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownMidWebFirst)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebFirst = append(d.OfflineWebFirst, hand)
hand = d.OnlineWebSecond[index]
d.OnlineWebSecond[index] = d.OnlineWebSecond[len(d.OnlineWebSecond)-1]
d.OnlineWebSecond[len(d.OnlineWebSecond)-1] = nil
d.OnlineWebSecond = d.OnlineWebSecond[:len(d.OnlineWebSecond)-1]
for i, h := range d.OnlineWebSecond {
h.Index = i
}
hand.Index = len(d.OfflineWebSecond)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebSecond = append(d.OfflineWebSecond, hand)
hand = d.OnlineWebThird[index]
d.OnlineWebThird[index] = d.OnlineWebThird[len(d.OnlineWebThird)-1]
d.OnlineWebThird[len(d.OnlineWebThird)-1] = nil
d.OnlineWebThird = d.OnlineWebThird[:len(d.OnlineWebThird)-1]
for i, h := range d.OnlineWebThird {
h.Index = i
}
hand.Index = len(d.OfflineWebThird)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebThird = append(d.OfflineWebThird, hand)
break
case UnknownHigh:
hand = d.UnknownHighWebFirst[index]
d.UnknownHighWebFirst[index] =
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1]
d.UnknownHighWebFirst[len(d.UnknownHighWebFirst)-1] = nil
d.UnknownHighWebFirst =
d.UnknownHighWebFirst[:len(d.UnknownHighWebFirst)-1]
for i, h := range d.UnknownHighWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownMidWebFirst)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebFirst = append(d.OfflineWebFirst, hand)
hand = d.UnknownHighWebSecond[index]
d.UnknownHighWebSecond[index] =
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1]
d.UnknownHighWebSecond[len(d.UnknownHighWebSecond)-1] = nil
d.UnknownHighWebSecond =
d.UnknownHighWebSecond[:len(d.UnknownHighWebSecond)-1]
for i, h := range d.UnknownHighWebSecond {
h.Index = i
}
hand.Index = len(d.OfflineWebSecond)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebSecond = append(d.OfflineWebSecond, hand)
hand = d.UnknownHighWebThird[index]
d.UnknownHighWebThird[index] =
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1]
d.UnknownHighWebThird[len(d.UnknownHighWebThird)-1] = nil
d.UnknownHighWebThird =
d.UnknownHighWebThird[:len(d.UnknownHighWebThird)-1]
for i, h := range d.UnknownHighWebThird {
h.Index = i
}
hand.Index = len(d.OfflineWebThird)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebThird = append(d.OfflineWebThird, hand)
break
case UnknownMid:
if time.Since(hand.LastState) > 1*time.Second {
hand = d.UnknownMidWebFirst[index]
d.UnknownMidWebFirst[index] =
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1]
d.UnknownMidWebFirst[len(d.UnknownMidWebFirst)-1] = nil
d.UnknownMidWebFirst =
d.UnknownMidWebFirst[:len(d.UnknownMidWebFirst)-1]
for i, h := range d.UnknownMidWebFirst {
h.Index = i
}
hand.Index = len(d.UnknownLowWebFirst)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebFirst = append(d.OfflineWebFirst, hand)
hand = d.UnknownMidWebSecond[index]
d.UnknownMidWebSecond[index] =
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1]
d.UnknownMidWebSecond[len(d.UnknownMidWebSecond)-1] = nil
d.UnknownMidWebSecond =
d.UnknownMidWebSecond[:len(d.UnknownMidWebSecond)-1]
for i, h := range d.UnknownMidWebSecond {
h.Index = i
}
hand.Index = len(d.OfflineWebSecond)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebSecond = append(d.OfflineWebSecond, hand)
hand = d.UnknownMidWebThird[index]
d.UnknownMidWebThird[index] =
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1]
d.UnknownMidWebThird[len(d.UnknownMidWebThird)-1] = nil
d.UnknownMidWebThird =
d.UnknownMidWebThird[:len(d.UnknownMidWebThird)-1]
for i, h := range d.UnknownMidWebThird {
h.Index = i
}
hand.Index = len(d.OfflineWebThird)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebThird = append(d.OfflineWebThird, hand)
}
break
case UnknownLow:
if time.Since(hand.LastState) > 2*time.Second {
hand = d.UnknownLowWebFirst[index]
d.UnknownLowWebFirst[index] =
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1]
d.UnknownLowWebFirst[len(d.UnknownLowWebFirst)-1] = nil
d.UnknownLowWebFirst =
d.UnknownLowWebFirst[:len(d.UnknownLowWebFirst)-1]
for i, h := range d.UnknownLowWebFirst {
h.Index = i
}
hand.Index = len(d.OfflineWebFirst)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebFirst = append(d.OfflineWebFirst, hand)
hand = d.UnknownLowWebSecond[index]
d.UnknownLowWebSecond[index] =
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1]
d.UnknownLowWebSecond[len(d.UnknownLowWebSecond)-1] = nil
d.UnknownLowWebSecond =
d.UnknownLowWebSecond[:len(d.UnknownLowWebSecond)-1]
for i, h := range d.UnknownLowWebSecond {
h.Index = i
}
hand.Index = len(d.OfflineWebSecond)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebSecond = append(d.OfflineWebSecond, hand)
hand = d.UnknownLowWebThird[index]
d.UnknownLowWebThird[index] =
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1]
d.UnknownLowWebThird[len(d.UnknownLowWebThird)-1] = nil
d.UnknownLowWebThird =
d.UnknownLowWebThird[:len(d.UnknownLowWebThird)-1]
for i, h := range d.UnknownLowWebThird {
h.Index = i
}
hand.Index = len(d.OfflineWebThird)
hand.State = Offline
hand.LastState = time.Now()
d.OfflineWebThird = append(d.OfflineWebThird, hand)
}
break
case Offline:
break
}
}
func (d *Domain) ResponseHandler(hand *Handler, resp *http.Response) error {
if hand.State != Online && resp.StatusCode < 500 {
d.upgradeHandler(hand)
}
return nil
}
func (d *Domain) ErrorHandlerFirst(hand *Handler, rw http.ResponseWriter,
r *http.Request, err error) {
if _, ok := err.(*WebSocketBlock); ok {
return
}
d.downgradeHandler(hand)
d.ServeHTTPSecond(rw, r)
}
func (d *Domain) ErrorHandlerSecond(hand *Handler, rw http.ResponseWriter,
r *http.Request, err error) {
if _, ok := err.(*WebSocketBlock); ok {
return
}
d.downgradeHandler(hand)
d.ServeHTTPThird(rw, r)
}
func (d *Domain) ErrorHandlerThird(hand *Handler, rw http.ResponseWriter,
r *http.Request, err error) {
if _, ok := err.(*WebSocketBlock); ok {
return
}
d.downgradeHandler(hand)
rw.WriteHeader(http.StatusBadGateway)
}
================================================
FILE: proxy/errortypes.go
================================================
package proxy
import "github.com/dropbox/godropbox/errors"
type WebSocketBlock struct {
errors.DropboxError
}
================================================
FILE: proxy/proxy.go
================================================
package proxy
import (
"bytes"
"net/http"
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Proxy struct {
Domains map[string]*Domain
lock sync.Mutex
}
type balancerState struct {
Balancer *balancer.Balancer
State *balancer.State
}
func (p *Proxy) ServeHTTP(hst string, rw http.ResponseWriter,
r *http.Request) {
domain := p.Domains[hst]
if domain == nil {
utils.WriteStatus(rw, 404)
return
}
domain.ServeHTTPFirst(rw, r)
}
func (p *Proxy) Update(db *database.Database, balncs []*balancer.Balancer) (
err error) {
domains := map[string]*Domain{}
domainsName := set.NewSet()
remDomains := []*Domain{}
states := []*balancerState{}
proxyProto := node.Self.Protocol
proxyPort := node.Self.Port
p.lock.Lock()
for _, balnc := range balncs {
if !balnc.State {
continue
}
onlineWeb := set.NewSet()
unknownHighWeb := set.NewSet()
unknownMidWeb := set.NewSet()
unknownLowWeb := set.NewSet()
offlineWeb := set.NewSet()
state := &balancer.State{
Timestamp: time.Now(),
Online: []string{},
UnknownHigh: []string{},
UnknownMid: []string{},
UnknownLow: []string{},
Offline: []string{},
}
for _, domain := range balnc.Domains {
if domains[domain.Domain] != nil {
conflictDomain := domains[domain.Domain]
logrus.WithFields(logrus.Fields{
"first_balancer_id": conflictDomain.Balancer.Id.Hex(),
"first_balancer_name": conflictDomain.Balancer.Name,
"second_balancer_id": balnc.Id.Hex(),
"second_balancer_name": balnc.Name,
"conflict_domain": domain.Domain,
}).Error("proxy: Balancer domain conflict")
continue
}
domainsName.Add(domain.Domain)
proxyDomain := &Domain{
SkipVerify: settings.Router.SkipVerify,
ProxyProto: proxyProto,
ProxyPort: proxyPort,
Balancer: balnc,
Domain: domain,
Requests: new(int32),
Retries: new(int32),
}
proxyDomain.CalculateHash()
curDomain := p.Domains[domain.Domain]
if curDomain != nil && curDomain.Balancer.Id == balnc.Id {
state.Requests += curDomain.RequestsTotal
state.Retries += curDomain.RetriesTotal
state.WebSockets += curDomain.WebSocketConns.Len()
curDomain.Lock.Lock()
for _, hand := range curDomain.OnlineWebFirst {
onlineWeb.Add(hand.Key)
}
for _, hand := range curDomain.UnknownHighWebFirst {
unknownHighWeb.Add(hand.Key)
}
for _, hand := range curDomain.UnknownMidWebFirst {
unknownMidWeb.Add(hand.Key)
}
for _, hand := range curDomain.UnknownLowWebFirst {
unknownLowWeb.Add(hand.Key)
}
for _, hand := range curDomain.OfflineWebFirst {
offlineWeb.Add(hand.Key)
}
if bytes.Equal(curDomain.Hash, proxyDomain.Hash) {
domains[domain.Domain] = curDomain
curDomain.Lock.Unlock()
continue
} else {
proxyDomain.Requests = curDomain.Requests
proxyDomain.RequestsPrev = curDomain.RequestsPrev
proxyDomain.RequestsTotal = curDomain.RequestsTotal
proxyDomain.Retries = curDomain.Retries
proxyDomain.RetriesPrev = curDomain.RetriesPrev
proxyDomain.RetriesTotal = curDomain.RetriesTotal
curDomain.Lock.Unlock()
remDomains = append(remDomains, curDomain)
}
}
proxyDomain.Init()
domains[domain.Domain] = proxyDomain
}
recorded := set.NewSet()
for keyInf := range offlineWeb.Iter() {
if recorded.Contains(keyInf) {
continue
}
recorded.Add(keyInf)
state.Offline = append(state.Offline, keyInf.(string))
}
for keyInf := range unknownLowWeb.Iter() {
if recorded.Contains(keyInf) {
continue
}
recorded.Add(keyInf)
state.UnknownLow = append(state.UnknownLow, keyInf.(string))
}
for keyInf := range unknownMidWeb.Iter() {
if recorded.Contains(keyInf) {
continue
}
recorded.Add(keyInf)
state.UnknownMid = append(state.UnknownMid, keyInf.(string))
}
for keyInf := range unknownHighWeb.Iter() {
if recorded.Contains(keyInf) {
continue
}
recorded.Add(keyInf)
state.UnknownHigh = append(state.UnknownHigh, keyInf.(string))
}
for keyInf := range onlineWeb.Iter() {
if recorded.Contains(keyInf) {
continue
}
recorded.Add(keyInf)
state.Online = append(state.Online, keyInf.(string))
}
states = append(states, &balancerState{
Balancer: balnc,
State: state,
})
}
curDomains := p.Domains
for name, domain := range curDomains {
if !domainsName.Contains(name) {
remDomains = append(remDomains, domain)
}
}
p.Domains = domains
p.lock.Unlock()
for _, domain := range remDomains {
domain.WebSocketConnsLock.Lock()
for socketInf := range domain.WebSocketConns.Iter() {
func() {
socket := socketInf.(*webSocketConn)
socket.Close()
}()
}
domain.WebSocketConns = set.NewSet()
domain.WebSocketConnsLock.Unlock()
}
for _, balncState := range states {
err = balncState.Balancer.CommitState(db, balncState.State)
if err != nil {
return
}
}
return
}
func (p *Proxy) syncCount() {
p.lock.Lock()
defer p.lock.Unlock()
domains := p.Domains
for _, dom := range domains {
req := dom.Requests
dom.Requests = new(int32)
reqPrev := dom.RequestsPrev
reqTotal := reqPrev[0] + reqPrev[1] + reqPrev[2] +
reqPrev[3] + reqPrev[4]
reqPrev[0] = reqPrev[1]
reqPrev[1] = reqPrev[2]
reqPrev[2] = reqPrev[3]
reqPrev[3] = reqPrev[4]
reqPrev[4] = int(*req)
reqTotal += int(*req)
dom.RequestsPrev = reqPrev
dom.RequestsTotal = reqTotal
ret := dom.Retries
dom.Retries = new(int32)
retPrev := dom.RetriesPrev
retTotal := retPrev[0] + retPrev[1] + retPrev[2] +
retPrev[3] + retPrev[4]
retPrev[0] = retPrev[1]
retPrev[1] = retPrev[2]
retPrev[2] = retPrev[3]
retPrev[3] = retPrev[4]
retPrev[4] = int(*ret)
retTotal += int(*ret)
dom.RetriesPrev = retPrev
dom.RetriesTotal = retTotal
}
}
func (p *Proxy) runCounter() {
for {
time.Sleep(10 * time.Second)
p.syncCount()
}
}
func (p *Proxy) healthCheck() {
p.lock.Lock()
defer p.lock.Unlock()
domains := p.Domains
for _, dom := range domains {
dom.Check()
}
}
func (p *Proxy) runHealthCheck() {
for {
time.Sleep(5 * time.Second)
p.healthCheck()
}
}
func (p *Proxy) Init() {
p.Domains = map[string]*Domain{}
go p.runCounter()
go p.runHealthCheck()
}
================================================
FILE: proxy/resolver.go
================================================
package proxy
import (
"context"
"net"
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/settings"
)
var (
ResolverLock sync.RWMutex
IpDatabase = set.NewSet()
ResolverCache = map[string]*Remote{}
HostNetwork *net.IPNet
NodePortNetwork *net.IPNet
)
type Remote struct {
Timestamp time.Time
Remote net.IP
}
func ResolverRefresh(db *database.Database) (err error) {
coll := db.Instances()
ipDatabase := set.NewSet()
ttl := time.Duration(settings.Router.ProxyResolverTtl) * time.Second
cursor, err := coll.Find(
db,
&bson.M{},
options.Find().
SetProjection(&bson.M{
"public_ips": 1,
"public_ips6": 1,
"cloud_private_ips": 1,
"cloud_public_ips": 1,
"cloud_public_ips6": 1,
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &instance.Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
for _, ipStr := range inst.PublicIps {
ip := net.ParseIP(ipStr)
if ip != nil {
ipDatabase.Add(ip.String())
}
}
for _, ipStr := range inst.PublicIps6 {
ip := net.ParseIP(ipStr)
if ip != nil {
ipDatabase.Add(ip.String())
}
}
for _, ipStr := range inst.CloudPublicIps {
ip := net.ParseIP(ipStr)
if ip != nil {
ipDatabase.Add(ip.String())
}
}
for _, ipStr := range inst.CloudPublicIps6 {
ip := net.ParseIP(ipStr)
if ip != nil {
ipDatabase.Add(ip.String())
}
}
for _, ipStr := range inst.CloudPrivateIps {
ip := net.ParseIP(ipStr)
if ip != nil {
ipDatabase.Add(ip.String())
}
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
_, hostNetwork, err := net.ParseCIDR(
settings.Hypervisor.HostNetwork)
if err != nil {
return
}
_, nodePortNetwork, err := net.ParseCIDR(
settings.Hypervisor.NodePortNetwork)
if err != nil {
return
}
now := time.Now()
ResolverLock.Lock()
for hostname, cached := range ResolverCache {
if now.Sub(cached.Timestamp) > ttl {
delete(ResolverCache, hostname)
}
}
IpDatabase = ipDatabase
HostNetwork = hostNetwork
NodePortNetwork = nodePortNetwork
ResolverLock.Unlock()
return
}
func ResolverValidate(ip net.IP) bool {
if IpDatabase.Contains(ip.String()) {
return true
}
if HostNetwork.Contains(ip) {
return true
}
if NodePortNetwork.Contains(ip) {
return true
}
return false
}
func Resolve(hostname string) (remote net.IP, err error) {
ResolverLock.RLock()
cached, ok := ResolverCache[hostname]
if ok {
ResolverLock.RUnlock()
remote = cached.Remote
return
}
ResolverLock.RUnlock()
ip := net.ParseIP(hostname)
if ip != nil {
ResolverLock.RLock()
contains := ResolverValidate(ip)
ResolverLock.RUnlock()
if !contains {
err = &errortypes.RequestError{
errors.New("proxy: Balancer resolved address not in database"),
}
return
}
remote = ip
} else {
ips, e := net.LookupIP(hostname)
if e != nil {
err = &errortypes.RequestError{
errors.Wrap(e, "proxy: Balancer resolve error"),
}
return
}
ResolverLock.RLock()
for _, ip := range ips {
if ResolverValidate(ip) {
remote = ip
break
}
}
ResolverLock.RUnlock()
if remote == nil {
err = &errortypes.RequestError{
errors.New("proxy: Balancer resolved address not in database"),
}
return
}
}
ResolverLock.Lock()
ResolverCache[hostname] = &Remote{
Timestamp: time.Now(),
Remote: remote,
}
ResolverLock.Unlock()
return
}
type StaticDialer struct {
dialer *net.Dialer
}
func (d *StaticDialer) DialContext(ctx context.Context, network, addr string) (
conn net.Conn, err error) {
host, port, err := net.SplitHostPort(addr)
if err != nil {
return nil, err
}
remote, err := Resolve(host)
if err != nil {
return
}
conn, err = d.dialer.DialContext(
ctx, network, net.JoinHostPort(remote.String(), port))
if err == nil {
return
}
return
}
func NewStaticDialer(dialer *net.Dialer) *StaticDialer {
return &StaticDialer{
dialer: dialer,
}
}
func init() {
_, HostNetwork, _ = net.ParseCIDR("0.0.0.0/32")
_, NodePortNetwork, _ = net.ParseCIDR("0.0.0.0/32")
}
================================================
FILE: proxy/reverse.go
================================================
package proxy
import (
"crypto/tls"
"fmt"
"log"
"net"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/gorilla/websocket"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/logger"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Handler struct {
Key string
Index int
State int
Domain *Domain
CheckUrl string
CheckClient *http.Client
LastState time.Time
LastOnlineState time.Time
BackendHost string
BackendProto string
BackendProtoWs string
RequestHost string
ForwardedProto string
ForwardedPort string
TlsConfig *tls.Config
Dialer *StaticDialer
WebSockets bool
WebSocketsUpgrader *websocket.Upgrader
ErrorHandler ErrorHandler
*httputil.ReverseProxy
}
func (h *Handler) ServeWS(rw http.ResponseWriter, r *http.Request) {
header := utils.CloneHeader(r.Header)
u := &url.URL{}
*u = *r.URL
u.Scheme = h.BackendProtoWs
u.Host = h.BackendHost
if h.RequestHost != "" {
r.Host = h.RequestHost
}
header.Set("X-Forwarded-For",
node.Self.GetRemoteAddr(r))
header.Set("X-Forwarded-Host", r.Host)
header.Set("X-Forwarded-Proto", h.ForwardedProto)
header.Set("X-Forwarded-Port", h.ForwardedPort)
header.Del("Upgrade")
header.Del("Connection")
header.Del("Sec-Websocket-Key")
header.Del("Sec-Websocket-Version")
header.Del("Sec-Websocket-Extensions")
var backConn *websocket.Conn
var backResp *http.Response
var err error
dialer := &websocket.Dialer{
NetDialContext: h.Dialer.DialContext,
Proxy: func(req *http.Request) (url *url.URL, err error) {
if h.RequestHost != "" {
req.Host = h.RequestHost
} else {
req.Host = r.Host
}
return
},
HandshakeTimeout: 45 * time.Second,
TLSClientConfig: h.TlsConfig,
}
backConn, backResp, err = dialer.Dial(u.String(), header)
if err != nil {
if backResp != nil {
err = &errortypes.RequestError{
errors.Wrapf(err, "proxy: WebSocket dial error %d",
backResp.StatusCode),
}
} else {
err = &errortypes.RequestError{
errors.Wrap(err, "proxy: WebSocket dial error"),
}
}
h.ErrorHandler(h, rw, r, err)
return
}
defer backConn.Close()
upgradeHeaders := http.Header{}
val := backResp.Header.Get("Sec-Websocket-Protocol")
if val != "" {
upgradeHeaders.Set("Sec-Websocket-Protocol", val)
}
val = backResp.Header.Get("Set-Cookie")
if val != "" {
upgradeHeaders.Set("Set-Cookie", val)
}
frontConn, err := h.WebSocketsUpgrader.Upgrade(rw, r, upgradeHeaders)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "proxy: WebSocket upgrade error"),
}
h.ErrorHandler(h, rw, r, err)
return
}
defer frontConn.Close()
conn := &webSocketConn{
front: frontConn,
back: backConn,
r: r,
}
conn.Run(h.Domain)
}
func (h *Handler) Serve(rw http.ResponseWriter, r *http.Request) {
if h.WebSockets && strings.ToLower(
r.Header.Get("Upgrade")) == "websocket" {
h.ServeWS(rw, r)
} else {
h.ServeHTTP(rw, r)
}
}
func NewHandler(index, state int, proxyProto string, proxyPort int,
domain *Domain, backend *balancer.Backend, respHandler RespHandler,
errHandler ErrorHandler) (hand *Handler) {
proxyPortStr := strconv.Itoa(proxyPort)
reqHost := domain.Domain.Host
backendProto := backend.Protocol
backendHost := utils.FormatHostPort(backend.Hostname, backend.Port)
backendProtoWs := ""
if backendProto == "https" {
backendProtoWs = "wss"
} else {
backendProtoWs = "ws"
}
handUrl := fmt.Sprintf(
"%s://%s:%d",
backend.Protocol,
backend.Hostname,
backend.Port,
)
checkUrl, err := url.Parse(handUrl)
if err != nil {
logrus.WithFields(logrus.Fields{
"balancer": domain.Balancer.Name,
"domain": domain.Domain.Domain,
"protocol": backend.Protocol,
"hostname": backend.Hostname,
"port": backend.Port,
"check_path": domain.Balancer.CheckPath,
}).Error("proxy: Error parsing balancer backend URL")
checkUrl, _ = url.Parse("http://0.0.0.0")
}
checkUrl.Path = domain.Balancer.CheckPath
dialTimeout := time.Duration(
settings.Router.DialTimeout) * time.Second
dialKeepAlive := time.Duration(
settings.Router.DialKeepAlive) * time.Second
maxIdleConns := settings.Router.MaxIdleConns
maxIdleConnsPerHost := settings.Router.MaxIdleConnsPerHost
idleConnTimeout := time.Duration(
settings.Router.IdleConnTimeout) * time.Second
handshakeTimeout := time.Duration(
settings.Router.HandshakeTimeout) * time.Second
continueTimeout := time.Duration(
settings.Router.ContinueTimeout) * time.Second
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
ServerName: backend.Hostname,
}
if domain.SkipVerify || net.ParseIP(backend.Hostname) != nil {
tlsConfig.InsecureSkipVerify = true
}
if domain.ClientCertificate != nil {
tlsConfig.Certificates = []tls.Certificate{
*domain.ClientCertificate,
}
}
writer := &logger.ErrorWriter{
Message: "proxy: Balancer server error",
Fields: logrus.Fields{
"balancer": domain.Balancer.Name,
"domain": domain.Domain.Domain,
"server": handUrl,
},
Filters: []string{
"context canceled",
},
}
dialer := NewStaticDialer(&net.Dialer{
Timeout: dialTimeout,
KeepAlive: dialKeepAlive,
DualStack: true,
})
checkClient := &http.Client{
Transport: &http.Transport{
DialContext: dialer.DialContext,
DisableKeepAlives: true,
TLSHandshakeTimeout: 5 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
},
},
Timeout: 5 * time.Second,
}
hand = &Handler{
Key: fmt.Sprintf("%s:%d", backend.Hostname, backend.Port),
Index: index,
State: state,
Domain: domain,
CheckUrl: checkUrl.String(),
CheckClient: checkClient,
BackendHost: backendHost,
BackendProto: backendProto,
BackendProtoWs: backendProtoWs,
RequestHost: reqHost,
ForwardedProto: proxyProto,
ForwardedPort: proxyPortStr,
WebSockets: domain.Balancer.WebSockets,
TlsConfig: tlsConfig,
Dialer: dialer,
ErrorHandler: errHandler,
ReverseProxy: &httputil.ReverseProxy{
Director: func(req *http.Request) {
req.Header.Set("X-Forwarded-Host", req.Host)
req.Header.Set("X-Forwarded-Proto", proxyProto)
req.Header.Set("X-Forwarded-Port", proxyPortStr)
if reqHost != "" {
req.Host = reqHost
}
req.URL.Scheme = backendProto
req.URL.Host = backendHost
},
Transport: &TransportFix{
transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: dialer.DialContext,
MaxIdleConns: maxIdleConns,
MaxIdleConnsPerHost: maxIdleConnsPerHost,
IdleConnTimeout: idleConnTimeout,
TLSHandshakeTimeout: handshakeTimeout,
ExpectContinueTimeout: continueTimeout,
TLSClientConfig: tlsConfig,
},
},
ErrorLog: log.New(writer, "", 0),
ModifyResponse: func(resp *http.Response) error {
return respHandler(hand, resp)
},
ErrorHandler: func(rw http.ResponseWriter,
r *http.Request, err error) {
errHandler(hand, rw, r, err)
},
},
}
if hand.WebSockets {
hand.WebSocketsUpgrader = &websocket.Upgrader{
HandshakeTimeout: time.Duration(
settings.Router.HandshakeTimeout) * time.Second,
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
return true
},
}
}
return
}
================================================
FILE: proxy/transport.go
================================================
package proxy
import (
"net/http"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/node"
)
type TransportFix struct {
transport *http.Transport
}
func (t *TransportFix) RoundTrip(r *http.Request) (
res *http.Response, err error) {
r.Header.Set("X-Forwarded-For", node.Self.GetRemoteAddr(r))
res, err = t.transport.RoundTrip(r)
if err != nil {
return
}
if res.StatusCode == http.StatusSwitchingProtocols {
err = &WebSocketBlock{
errors.New("proxy: Blocking websocket connection"),
}
return
}
return
}
================================================
FILE: proxy/types.go
================================================
package proxy
import (
"net/http"
)
type RespHandler func(hand *Handler, resp *http.Response) (err error)
type ErrorHandler func(hand *Handler, rw http.ResponseWriter,
r *http.Request, err error)
================================================
FILE: proxy/utils.go
================================================
package proxy
import (
"net/http"
"github.com/sirupsen/logrus"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
)
func WriteError(w http.ResponseWriter, r *http.Request, code int, err error) {
http.Error(w, utils.GetStatusMessage(code), code)
logrus.WithFields(logrus.Fields{
"client": node.Self.GetRemoteAddr(r),
"error": err,
}).Error("proxy: Serve error")
}
================================================
FILE: proxy/ws.go
================================================
package proxy
import (
"fmt"
"net/http"
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
)
type webSocketConn struct {
r *http.Request
back *websocket.Conn
front *websocket.Conn
}
func (w *webSocketConn) Run(domain *Domain) {
domain.WebSocketConnsLock.Lock()
domain.WebSocketConns.Add(w)
domain.WebSocketConnsLock.Unlock()
defer func() {
domain.WebSocketConnsLock.Lock()
domain.WebSocketConns.Remove(w)
domain.WebSocketConnsLock.Unlock()
}()
wait := make(chan bool, 4)
go func() {
defer func() {
rec := recover()
if rec != nil {
logrus.WithFields(logrus.Fields{
"panic": rec,
}).Error("proxy: WebSocket back panic")
wait <- true
}
}()
for {
msgType, msg, err := w.front.ReadMessage()
if err != nil {
closeMsg := websocket.FormatCloseMessage(
websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
closeMsg = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
_ = w.back.WriteMessage(websocket.CloseMessage, closeMsg)
break
}
_ = w.back.WriteMessage(msgType, msg)
}
wait <- true
}()
go func() {
defer func() {
rec := recover()
if rec != nil {
logrus.WithFields(logrus.Fields{
"panic": rec,
}).Error("proxy: WebSocket front panic")
wait <- true
}
}()
for {
msgType, msg, err := w.back.ReadMessage()
if err != nil {
closeMsg := websocket.FormatCloseMessage(
websocket.CloseNormalClosure, fmt.Sprintf("%v", err))
if e, ok := err.(*websocket.CloseError); ok {
if e.Code != websocket.CloseNoStatusReceived {
closeMsg = websocket.FormatCloseMessage(e.Code, e.Text)
}
}
_ = w.front.WriteMessage(websocket.CloseMessage, closeMsg)
break
}
_ = w.front.WriteMessage(msgType, msg)
}
wait <- true
}()
<-wait
w.Close()
}
func (w *webSocketConn) Close() {
defer func() {
recover()
}()
if w.back != nil {
w.back.Close()
}
if w.front != nil {
w.front.Close()
}
}
================================================
FILE: qemu/constants.go
================================================
package qemu
const systemdTemplate = `# PritunlData=%s
[Unit]
Description=Pritunl Cloud Virtual Machine
After=network.target
[Service]%s
Environment=XDG_CACHE_HOME=%s
Type=simple
User=root
ExecStart=%s
TimeoutStopSec=5
PrivateTmp=%s
ProtectHome=%s
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
PrivateIPC=true
NetworkNamespacePath=/var/run/netns/%s
`
const systemdTemplateExternalNet = `# PritunlData=%s
[Unit]
Description=Pritunl Cloud Virtual Machine
After=network.target
[Service]%s
Environment=XDG_CACHE_HOME=%s
Type=simple
User=root
ExecStart=%s
TimeoutStopSec=5
PrivateTmp=%s
ProtectHome=%s
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
PrivateIPC=true
`
================================================
FILE: qemu/data.go
================================================
package qemu
import (
"os"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/dhcpc"
"github.com/pritunl/pritunl-cloud/dhcps"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/guest"
"github.com/pritunl/pritunl-cloud/hugepages"
"github.com/pritunl/pritunl-cloud/imds"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/permission"
"github.com/pritunl/pritunl-cloud/qmp"
"github.com/pritunl/pritunl-cloud/qms"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/tpm"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/virtiofs"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
func initDirs(virt *vm.VirtualMachine) (err error) {
vmPath := paths.GetVmPath(virt.Id)
err = utils.ExistsMkdir(settings.Hypervisor.LibPath, 0755)
if err != nil {
return
}
err = utils.ExistsMkdir(settings.Hypervisor.RunPath, 0755)
if err != nil {
return
}
err = utils.ExistsMkdir(paths.GetImdsPath(), 0755)
if err != nil {
return
}
err = utils.ExistsMkdir(vmPath, 0755)
if err != nil {
return
}
return
}
func initHugepage(virt *vm.VirtualMachine) (err error) {
if !virt.Hugepages {
return
}
err = hugepages.UpdateHugepagesSize()
if err != nil {
return
}
hugepagesPath := paths.GetHugepagePath(virt.Id)
_ = os.Remove(hugepagesPath)
return
}
func cleanRun(virt *vm.VirtualMachine) (err error) {
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
runPath := paths.GetInstRunPath(virt.Id)
pidPath := paths.GetPidPath(virt.Id)
sockPath := paths.GetSockPath(virt.Id)
qmpSockPath := paths.GetQmpSockPath(virt.Id)
guestPath := paths.GetGuestPath(virt.Id)
err = utils.RemoveAll(runPath)
if err != nil {
return
}
err = utils.RemoveAll(pidPath)
if err != nil {
return
}
err = utils.RemoveAll(sockPath)
if err != nil {
return
}
err = utils.RemoveAll(qmpSockPath)
if err != nil {
return
}
err = utils.RemoveAll(guestPath)
if err != nil {
return
}
return
}
func initCache(virt *vm.VirtualMachine) (err error) {
err = utils.ExistsMkdir(paths.GetCachesDir(), 0755)
if err != nil {
return
}
err = utils.ExistsMkdir(paths.GetCacheDir(virt.Id), 0700)
if err != nil {
return
}
return
}
func initRun(virt *vm.VirtualMachine) (err error) {
runPath := paths.GetInstRunPath(virt.Id)
err = utils.ExistsMkdir(runPath, 0700)
if err != nil {
return
}
return
}
func initPermissions(virt *vm.VirtualMachine) (err error) {
err = permission.InitVirt(virt)
if err != nil {
return
}
err = permission.InitImds(virt)
if err != nil {
return
}
for _, mount := range virt.Mounts {
shareId := paths.GetShareId(virt.Id, mount.Name)
err = permission.InitMount(virt, shareId)
if err != nil {
return
}
}
return
}
func writeOvmfVars(virt *vm.VirtualMachine) (err error) {
if !virt.Uefi {
return
}
ovmfVarsPath := paths.GetOvmfVarsPath(virt.Id)
ovmfVarsPathSource, err := paths.FindOvmfVarsPath(virt.SecureBoot)
if err != nil {
return
}
err = utils.ExistsMkdir(paths.GetOvmfDir(), 0755)
if err != nil {
return
}
err = utils.Exec("", "cp", ovmfVarsPathSource, ovmfVarsPath)
if err != nil {
return
}
err = utils.Chmod(ovmfVarsPath, 0600)
if err != nil {
return
}
return
}
func activateDisks(db *database.Database,
virt *vm.VirtualMachine) (err error) {
for _, virtDsk := range virt.Disks {
dsk, e := disk.Get(db, virtDsk.Id)
if e != nil {
err = e
return
}
err = data.ActivateDisk(db, dsk)
if err != nil {
return
}
}
return
}
func deactivateDisks(db *database.Database,
virt *vm.VirtualMachine) (err error) {
for _, virtDsk := range virt.Disks {
dsk, e := disk.Get(db, virtDsk.Id)
if e != nil {
err = e
return
}
err = data.DeactivateDisk(db, dsk)
if err != nil {
return
}
}
return
}
func writeService(virt *vm.VirtualMachine) (err error) {
unitPath := paths.GetUnitPath(virt.Id)
qm, err := NewQemu(virt)
if err != nil {
return
}
output, err := qm.Marshal()
if err != nil {
return
}
err = utils.CreateWrite(unitPath, output, 0644)
if err != nil {
return
}
err = systemd.Reload()
if err != nil {
return
}
return
}
func Destroy(db *database.Database, virt *vm.VirtualMachine) (err error) {
vmPath := paths.GetVmPath(virt.Id)
unitName := paths.GetUnitName(virt.Id)
unitPath := paths.GetUnitPath(virt.Id)
unitPathServer4 := paths.GetUnitPathDhcp4(virt.Id, 0)
unitPathServer6 := paths.GetUnitPathDhcp6(virt.Id, 0)
unitPathServerNdp := paths.GetUnitPathNdp(virt.Id, 0)
tpmPath := paths.GetTpmPath(virt.Id)
runPath := paths.GetInstRunPath(virt.Id)
unitPathTpm := paths.GetUnitPathTpm(virt.Id)
unitPathImds := paths.GetUnitPathImds(virt.Id)
unitPathDhcpc := paths.GetUnitPathDhcpc(virt.Id)
unitPathShares := paths.GetUnitPathShares(virt.Id)
sockPath := paths.GetSockPath(virt.Id)
sockQmpPath := paths.GetQmpSockPath(virt.Id)
// TODO Backward compatibility
sockPathOld := paths.GetSockPath(virt.Id)
guestPath := paths.GetGuestPath(virt.Id)
// TODO Backward compatibility
guestPathOld := paths.GetGuestPathOld(virt.Id)
pidPath := paths.GetPidPath(virt.Id)
// TODO Backward compatibility
pidPathOld := paths.GetPidPathOld(virt.Id)
ovmfVarsPath := paths.GetOvmfVarsPath(virt.Id)
hugepagesPath := paths.GetHugepagePath(virt.Id)
cachePath := paths.GetCacheDir(virt.Id)
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("qemu: Destroying virtual machine")
exists, err := utils.Exists(unitPath)
if err != nil {
return
}
if exists {
vrt, e := GetVmInfo(db, virt.Id, false, true)
if e != nil {
err = e
return
}
if vrt != nil && vrt.State == vm.Running {
guestShutdown := true
err = guest.Shutdown(virt.Id)
if err != nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": err,
}).Warn("qemu: Failed to send shutdown to guest agent")
err = nil
guestShutdown = false
}
logged := false
for i := 0; i < 10; i++ {
err = qmp.Shutdown(virt.Id)
if err == nil {
break
}
if guestShutdown {
err = nil
break
}
if !logged {
logged = true
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": err,
}).Warn(
"qemu: Failed to send shutdown to virtual machine")
}
time.Sleep(500 * time.Millisecond)
}
shutdown := false
if err != nil {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
"error": err,
}).Error("qemu: Power off virtual machine error")
err = nil
} else {
for i := 0; i < settings.Hypervisor.StopTimeout; i++ {
vrt, err = GetVmInfo(db, virt.Id, false, true)
if err != nil {
return
}
if vrt == nil || vrt.State == vm.Stopped ||
vrt.State == vm.Failed {
if vrt != nil {
err = vrt.Commit(db)
if err != nil {
return
}
}
shutdown = true
break
}
time.Sleep(1 * time.Second)
if (i+1)%15 == 0 {
go func() {
qmp.Shutdown(virt.Id)
qms.Shutdown(virt.Id)
}()
}
}
}
if !shutdown {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Warning("qemu: Force power off virtual machine")
}
}
err = systemd.Stop(unitName)
if err != nil {
return
}
}
time.Sleep(1 * time.Second)
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
err = NetworkConfClear(db, virt)
if err != nil {
return
}
time.Sleep(3 * time.Second)
for _, dsk := range virt.Disks {
ds, e := disk.Get(db, dsk.GetId())
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
err = data.DeactivateDisk(db, ds)
if err != nil {
return
}
if ds.Index == "0" && ds.SourceInstance == virt.Id {
err = disk.Delete(db, ds.Id)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
} else {
err = disk.Detach(db, dsk.GetId())
if err != nil {
return
}
}
}
for i, dsk := range virt.DriveDevices {
if dsk.Type != vm.Lvm {
continue
}
dskId, ok := utils.ParseObjectId(dsk.Id)
if dskId.IsZero() || !ok {
err = &errortypes.ParseError{
errors.Newf("qemu: Failed to parse LVM disk ID '%s'", dsk.Id),
}
return
}
ds, e := disk.Get(db, dskId)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
if i == 0 && ds.SourceInstance == virt.Id {
err = disk.Delete(db, ds.Id)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
continue
}
return
}
} else {
err = disk.Detach(db, dskId)
if err != nil {
return
}
}
}
err = utils.RemoveAll(vmPath)
if err != nil {
return
}
err = utils.RemoveAll(unitPath)
if err != nil {
return
}
err = utils.RemoveAll(tpmPath)
if err != nil {
return
}
err = utils.RemoveAll(runPath)
if err != nil {
return
}
err = utils.RemoveAll(unitPathTpm)
if err != nil {
return
}
err = utils.RemoveAll(unitPathImds)
if err != nil {
return
}
err = utils.RemoveAll(unitPathDhcpc)
if err != nil {
return
}
err = utils.RemoveAll(unitPathServer4)
if err != nil {
return
}
err = utils.RemoveAll(unitPathServer6)
if err != nil {
return
}
err = utils.RemoveAll(unitPathServerNdp)
if err != nil {
return
}
_, err = utils.RemoveWildcard(unitPathShares)
if err != nil {
return
}
err = utils.RemoveAll(sockPath)
if err != nil {
return
}
err = utils.RemoveAll(sockQmpPath)
if err != nil {
return
}
// TODO Backward compatibility
err = utils.RemoveAll(sockPathOld)
if err != nil {
return
}
err = utils.RemoveAll(guestPath)
if err != nil {
return
}
// TODO Backward compatibility
err = utils.RemoveAll(guestPathOld)
if err != nil {
return
}
err = utils.RemoveAll(pidPath)
if err != nil {
return
}
// TODO Backward compatibility
err = utils.RemoveAll(pidPathOld)
if err != nil {
return
}
err = utils.RemoveAll(paths.GetInitPath(virt.Id))
if err != nil {
return
}
err = utils.RemoveAll(unitPath)
if err != nil {
return
}
err = utils.RemoveAll(ovmfVarsPath)
if err != nil {
return
}
err = utils.RemoveAll(hugepagesPath)
if err != nil {
return
}
err = utils.RemoveAll(cachePath)
if err != nil {
return
}
err = permission.UserDelete(virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
store.RemAddress(virt.Id)
store.RemRoutes(virt.Id)
store.RemArp(virt.Id)
return
}
func Cleanup(db *database.Database, virt *vm.VirtualMachine) {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("qemu: Stopped virtual machine")
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
hugepagesPath := paths.GetHugepagePath(virt.Id)
_ = os.Remove(hugepagesPath)
err := NetworkConfClear(db, virt)
if err != nil {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
"error": err,
}).Error("qemu: Failed to cleanup virtual machine network")
}
time.Sleep(3 * time.Second)
err = deactivateDisks(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
================================================
FILE: qemu/disk.go
================================================
package qemu
import (
"time"
"github.com/pritunl/pritunl-cloud/qmp"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/vm"
)
func UpdateVmDisk(virt *vm.VirtualMachine) (err error) {
for i := 0; i < 10; i++ {
if virt.State == vm.Running {
_, disks, e := qmp.GetDisks(virt.Id)
if e != nil {
if i < 9 {
time.Sleep(300 * time.Millisecond)
_ = UpdateState(virt)
continue
}
err = e
return
}
virt.Disks = disks
store.SetDisks(virt.Id, disks)
}
break
}
return
}
================================================
FILE: qemu/manage.go
================================================
package qemu
import (
"encoding/json"
"fmt"
"io/ioutil"
"regexp"
"strings"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/cloudinit"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/dhcps"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/qmp"
"github.com/pritunl/pritunl-cloud/qms"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/tpm"
"github.com/pritunl/pritunl-cloud/virtiofs"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
"github.com/sirupsen/logrus"
)
var (
serviceReg = regexp.MustCompile("pritunl_cloud_([a-z0-9]+).service")
)
type InfoCache struct {
Timestamp time.Time
Virt *vm.VirtualMachine
}
func GetVmInfo(db *database.Database, vmId bson.ObjectID,
queryQms, force bool) (virt *vm.VirtualMachine, err error) {
refreshRate := time.Duration(
settings.Hypervisor.RefreshRate) * time.Second
virtStore, ok := store.GetVirt(vmId)
if !ok {
unitPath := paths.GetUnitPath(vmId)
unitData, e := ioutil.ReadFile(unitPath)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to read service"),
}
_ = ForcePowerOffErr(db, virt, err)
return
}
virt = &vm.VirtualMachine{}
for _, line := range strings.Split(string(unitData), "\n") {
if !strings.HasPrefix(line, "PritunlData=") &&
!strings.HasPrefix(line, "# PritunlData=") {
continue
}
lineSpl := strings.SplitN(line, "=", 2)
if len(lineSpl) != 2 || len(lineSpl[1]) < 6 {
continue
}
err = json.Unmarshal([]byte(lineSpl[1]), virt)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qemu: Failed to parse service data"),
}
_ = ForcePowerOffErr(db, virt, err)
return
}
break
}
if virt.Id.IsZero() {
virt = nil
return
}
_ = UpdateState(virt)
} else {
virt = &virtStore.Virt
if force || virt.State != vm.Running ||
time.Since(virtStore.Timestamp) > 6*time.Second {
_ = UpdateState(virt)
}
}
if virt.State == vm.Running && queryQms {
virt.DisksAvailable = true
disksUpdated := false
disksStore, ok := store.GetDisks(vmId)
if !ok || time.Since(disksStore.Timestamp) > refreshRate {
for i := 0; i < 10; i++ {
if virt.State == vm.Running {
info, disks, e := qmp.GetDisks(vmId)
if e != nil {
if i < 9 {
time.Sleep(300 * time.Millisecond)
_ = UpdateState(virt)
continue
}
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"error": e,
}).Error("qemu: Failed to get VM disk state")
virt.DisksAvailable = false
e = nil
break
}
virt.QemuVersion = fmt.Sprintf(
"%d.%d.%d",
info.VersionMajor,
info.VersionMinor,
info.VersionMicro,
)
virt.Disks = disks
store.SetDisks(vmId, disks)
disksUpdated = true
}
break
}
}
if ok && !disksUpdated {
disks := []*vm.Disk{}
for _, dsk := range disksStore.Disks {
disks = append(disks, dsk.Copy())
}
virt.Disks = disks
}
}
if virt.State == vm.Running && queryQms && node.Self.UsbPassthrough {
virt.UsbDevicesAvailable = true
usbsUpdated := false
usbsStore, ok := store.GetUsbs(vmId)
if !ok || time.Since(usbsStore.Timestamp) > refreshRate {
for i := 0; i < 10; i++ {
if virt.State == vm.Running {
usbs, e := qms.GetUsbDevices(vmId)
if e != nil {
if i < 9 {
time.Sleep(300 * time.Millisecond)
_ = UpdateState(virt)
continue
}
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"error": e,
}).Error("qemu: Failed to get VM usb state")
virt.UsbDevicesAvailable = false
e = nil
break
}
virt.UsbDevices = usbs
store.SetUsbs(vmId, usbs)
usbsUpdated = true
}
break
}
}
if ok && !usbsUpdated {
usbs := []*vm.UsbDevice{}
for _, usb := range usbsStore.Usbs {
usbs = append(usbs, usb.Copy())
}
virt.UsbDevices = usbs
}
}
addrStore, ok := store.GetAddress(virt.Id)
if !ok {
addr := ""
addr6 := ""
namespace := vm.GetNamespace(virt.Id, 0)
nodeNetworkMode := node.Self.NetworkMode
if nodeNetworkMode == "" {
nodeNetworkMode = node.Dhcp
}
nodeNetworkMode6 := node.Self.NetworkMode6
if nodeNetworkMode6 == "" {
nodeNetworkMode6 = node.Dhcp
}
ifaceExternal := vm.GetIfaceExternal(virt.Id, 0)
if nodeNetworkMode != node.Disabled &&
nodeNetworkMode != node.Cloud {
address, address6, e := iproute.AddressGetIfaceMod(
namespace, ifaceExternal)
if e != nil {
if addrStore != nil {
if len(virt.NetworkAdapters) > 0 {
virt.NetworkAdapters[0].IpAddress = addrStore.Addr
virt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6
}
} else {
err = e
_ = ForcePowerOffErr(db, virt, err)
}
return
}
if address != nil {
addr = address.Local
}
if address6 != nil {
addr6 = address6.Local
}
} else if nodeNetworkMode6 != node.Disabled &&
nodeNetworkMode6 != node.Cloud {
_, address6, e := iproute.AddressGetIfaceMod(
namespace, ifaceExternal)
if e != nil {
if addrStore != nil {
if len(virt.NetworkAdapters) > 0 {
virt.NetworkAdapters[0].IpAddress = addrStore.Addr
virt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6
}
} else {
err = e
_ = ForcePowerOffErr(db, virt, err)
}
return
}
if address6 != nil {
addr6 = address6.Local
}
}
if len(virt.NetworkAdapters) > 0 {
virt.NetworkAdapters[0].IpAddress = addr
virt.NetworkAdapters[0].IpAddress6 = addr6
}
store.SetAddress(virt.Id, addr, addr6)
} else {
if len(virt.NetworkAdapters) > 0 {
virt.NetworkAdapters[0].IpAddress = addrStore.Addr
virt.NetworkAdapters[0].IpAddress6 = addrStore.Addr6
}
}
return
}
func updateState(virt *vm.VirtualMachine, retry bool) (err error) {
unitName := paths.GetUnitName(virt.Id)
state, timestamp, err := systemd.GetState(unitName)
if err != nil {
return
}
switch state {
case "active":
virt.State = vm.Running
break
case "deactivating":
virt.State = vm.Running
break
case "inactive":
virt.State = vm.Stopped
break
case "failed":
virt.State = vm.Failed
break
case "unknown":
virt.State = vm.Stopped
break
default:
if retry {
time.Sleep(2 * time.Second)
err = updateState(virt, false)
return
} else {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"state": state,
}).Info("qemu: Unknown virtual machine state")
virt.State = vm.Failed
}
break
}
virt.Timestamp = timestamp
store.SetVirt(virt.Id, virt)
return
}
func UpdateState(virt *vm.VirtualMachine) (err error) {
err = updateState(virt, true)
if err != nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": err,
}).Error("deploy: Error updating VM state")
return
}
return
}
func SetState(virt *vm.VirtualMachine, state string) {
virt.State = state
store.SetVirt(virt.Id, virt)
}
func GetVms(db *database.Database) (
virts []*vm.VirtualMachine, err error) {
systemdPath := settings.Hypervisor.SystemdPath
virts = []*vm.VirtualMachine{}
items, err := ioutil.ReadDir(systemdPath)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to read systemd directory"),
}
return
}
units := []string{}
for _, item := range items {
if strings.HasPrefix(item.Name(), "pritunl_cloud") {
units = append(units, item.Name())
}
}
waiter := sync.WaitGroup{}
virtsLock := sync.Mutex{}
for _, unit := range units {
match := serviceReg.FindStringSubmatch(unit)
if match == nil || len(match) != 2 {
continue
}
vmId, err := bson.ObjectIDFromHex(match[1])
if err != nil {
continue
}
waiter.Add(1)
go func() {
defer waiter.Done()
virt, e := GetVmInfo(db, vmId, true, false)
if e != nil {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"error": e,
}).Error("qemu: Failed to get VM state")
return
}
if virt != nil {
virtsLock.Lock()
virts = append(virts, virt)
virtsLock.Unlock()
}
}()
}
waiter.Wait()
return
}
func Wait(db *database.Database, virt *vm.VirtualMachine) (err error) {
unitName := paths.GetUnitName(virt.Id)
for i := 0; i < settings.Hypervisor.StartTimeout; i++ {
err = UpdateState(virt)
if err != nil {
return
}
if virt.State == vm.Running {
break
}
time.Sleep(1 * time.Second)
}
if virt.State != vm.Running {
err = systemd.Stop(unitName)
if err != nil {
return
}
err = &errortypes.TimeoutError{
errors.New("qemu: Power on timeout"),
}
return
}
return
}
func Create(db *database.Database, inst *instance.Instance,
virt *vm.VirtualMachine) (err error) {
unitName := paths.GetUnitName(virt.Id)
if constants.Interrupt {
return
}
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("qemu: Creating virtual machine")
virt.State = vm.Provisioning
err = virt.Commit(db)
if err != nil {
return
}
err = inst.InitUnixId(db)
if err != nil {
return
}
virt.UnixId = inst.UnixId
if inst.Vnc {
err = inst.InitVncDisplay(db)
if err != nil {
return
}
virt.VncDisplay = inst.VncDisplay
}
if inst.Spice {
err = inst.InitSpicePort(db)
if err != nil {
return
}
virt.SpicePort = inst.SpicePort
}
err = initDirs(virt)
if err != nil {
return
}
err = cleanRun(virt)
if err != nil {
return
}
dsk, err := disk.GetInstanceIndex(db, inst.Id, "0")
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
dsk = nil
err = nil
} else {
return
}
}
if dsk == nil {
dsk = &disk.Disk{
Id: bson.NewObjectID(),
Name: inst.Name,
State: disk.Available,
Type: virt.DiskType,
Pool: virt.DiskPool,
Node: node.Self.Id,
Deployment: inst.Deployment,
Organization: inst.Organization,
Instance: inst.Id,
Datacenter: node.Self.Datacenter,
Zone: node.Self.Zone,
SourceInstance: inst.Id,
Image: virt.Image,
Backing: inst.ImageBacking,
Index: "0",
Size: inst.InitDiskSize,
DeleteProtection: inst.DeleteProtection,
}
errData, e := dsk.Validate(db)
if e != nil {
err = e
return
}
if errData != nil {
err = errData.GetError()
return
}
backingImage := ""
newSize := 0
if virt.Image.IsZero() {
newSize, backingImage, err = data.CreateDisk(db, dsk)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("deploy: Failed to provision disk")
return
}
} else {
img, e := image.Get(db, dsk.Image)
if e != nil {
err = e
return
}
dsk.SystemType = img.GetSystemType()
dsk.SystemKind = img.GetSystemKind()
newSize, backingImage, err = data.WriteImage(db, dsk)
if err != nil {
return
}
}
if newSize != 0 {
dsk.Size = newSize
}
dsk.BackingImage = backingImage
err = dsk.Insert(db)
if err != nil {
return
}
_ = event.PublishDispatch(db, "disk.change")
if virt.DiskType == disk.Lvm {
pl, e := pool.Get(db, dsk.Pool)
if e != nil {
err = e
return
}
virt.DriveDevices = append(virt.DriveDevices, &vm.DriveDevice{
Id: dsk.Id.Hex(),
Type: vm.Lvm,
VgName: pl.VgName,
LvName: dsk.Id.Hex(),
})
} else {
virt.Disks = append(virt.Disks, &vm.Disk{
Id: dsk.Id,
Index: 0,
Path: paths.GetDiskPath(dsk.Id),
})
}
}
if len(virt.NetworkAdapters) == 0 {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing network adapters"),
}
return
}
adapter := virt.NetworkAdapters[0]
if adapter.Vpc.IsZero() {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing VPC"),
}
return
}
if adapter.Subnet.IsZero() {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing VPC subnet"),
}
return
}
dc, err := datacenter.Get(db, node.Self.Datacenter)
if err != nil {
return
}
zne, err := zone.Get(db, node.Self.Zone)
if err != nil {
return
}
vc, err := vpc.Get(db, adapter.Vpc)
if err != nil {
return
}
err = virt.GenerateImdsSecret()
if err != nil {
return
}
err = cloudinit.Write(db, inst, virt, dc, zne, vc, true)
if err != nil {
return
}
err = initCache(virt)
if err != nil {
return
}
err = initHugepage(virt)
if err != nil {
return
}
err = writeOvmfVars(virt)
if err != nil {
return
}
err = activateDisks(db, virt)
if err != nil {
return
}
err = writeService(virt)
if err != nil {
return
}
err = initRun(virt)
if err != nil {
return
}
virt.State = vm.Starting
err = virt.Commit(db)
if err != nil {
return
}
err = virtiofs.StartAll(db, virt)
if err != nil {
return
}
err = initPermissions(virt)
if err != nil {
return
}
if virt.DhcpServer {
err = dhcps.Start(db, virt, dc, zne, vc)
if err != nil {
return
}
} else {
err = dhcps.Stop(virt)
if err != nil {
return
}
}
if virt.Tpm {
err = tpm.Start(db, virt)
if err != nil {
return
}
} else {
err = tpm.Stop(virt)
if err != nil {
return
}
}
err = systemd.Start(unitName)
if err != nil {
return
}
err = Wait(db, virt)
if err != nil {
return
}
if virt.Vnc {
err = qmp.VncPassword(virt.Id, inst.VncPassword)
if err != nil {
return
}
}
if virt.Spice {
err = qmp.SetPassword(virt.Id, qmp.Spice, inst.SpicePassword)
if err != nil {
return
}
}
err = NetworkConf(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
================================================
FILE: qemu/network.go
================================================
package qemu
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/dhcps"
"github.com/pritunl/pritunl-cloud/netconf"
"github.com/pritunl/pritunl-cloud/vm"
)
func NetworkConfClear(db *database.Database,
virt *vm.VirtualMachine) (err error) {
err = dhcps.Stop(virt)
if err != nil {
return
}
nc := netconf.New(virt)
err = nc.Clean(db)
if err != nil {
return
}
return
}
func NetworkConf(db *database.Database,
virt *vm.VirtualMachine) (err error) {
nc := netconf.New(virt)
err = nc.Init(db)
if err != nil {
return
}
return
}
================================================
FILE: qemu/power.go
================================================
package qemu
import (
"os"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/cloudinit"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/dhcpc"
"github.com/pritunl/pritunl-cloud/dhcps"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/guest"
"github.com/pritunl/pritunl-cloud/imds"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/qmp"
"github.com/pritunl/pritunl-cloud/qms"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/tpm"
"github.com/pritunl/pritunl-cloud/virtiofs"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
"github.com/sirupsen/logrus"
)
func PowerOn(db *database.Database, inst *instance.Instance,
virt *vm.VirtualMachine) (err error) {
unitName := paths.GetUnitName(virt.Id)
if constants.Interrupt {
return
}
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("qemu: Starting virtual machine")
err = inst.InitUnixId(db)
if err != nil {
return
}
virt.UnixId = inst.UnixId
if inst.Vnc {
err = inst.InitVncDisplay(db)
if err != nil {
return
}
virt.VncDisplay = inst.VncDisplay
}
if inst.Spice {
err = inst.InitSpicePort(db)
if err != nil {
return
}
virt.SpicePort = inst.SpicePort
}
err = initDirs(virt)
if err != nil {
return
}
err = cleanRun(virt)
if err != nil {
return
}
if len(virt.NetworkAdapters) == 0 {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing network adapters"),
}
return
}
adapter := virt.NetworkAdapters[0]
if adapter.Vpc.IsZero() {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing VPC"),
}
return
}
if adapter.Subnet.IsZero() {
err = &errortypes.NotFoundError{
errors.Wrap(err, "cloudinit: Instance missing VPC subnet"),
}
return
}
dc, err := datacenter.Get(db, node.Self.Datacenter)
if err != nil {
return
}
zne, err := zone.Get(db, node.Self.Zone)
if err != nil {
return
}
vc, err := vpc.Get(db, adapter.Vpc)
if err != nil {
return
}
err = virt.GenerateImdsSecret()
if err != nil {
return
}
err = cloudinit.Write(db, inst, virt, dc, zne, vc, false)
if err != nil {
return
}
err = initCache(virt)
if err != nil {
return
}
err = initHugepage(virt)
if err != nil {
return
}
err = writeOvmfVars(virt)
if err != nil {
return
}
err = activateDisks(db, virt)
if err != nil {
return
}
err = writeService(virt)
if err != nil {
return
}
err = initRun(virt)
if err != nil {
return
}
err = virtiofs.StartAll(db, virt)
if err != nil {
return
}
err = initPermissions(virt)
if err != nil {
return
}
if virt.DhcpServer {
err = dhcps.Start(db, virt, dc, zne, vc)
if err != nil {
return
}
} else {
err = dhcps.Stop(virt)
if err != nil {
return
}
}
if virt.Tpm {
err = tpm.Start(db, virt)
if err != nil {
return
}
} else {
err = tpm.Stop(virt)
if err != nil {
return
}
}
err = systemd.Start(unitName)
if err != nil {
return
}
err = Wait(db, virt)
if err != nil {
return
}
if virt.Vnc {
err = qmp.VncPassword(virt.Id, inst.VncPassword)
if err != nil {
return
}
}
if virt.Spice {
err = qmp.SetPassword(virt.Id, qmp.Spice, inst.SpicePassword)
if err != nil {
return
}
}
err = NetworkConf(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
func PowerOff(db *database.Database, virt *vm.VirtualMachine) (err error) {
unitName := paths.GetUnitName(virt.Id)
if constants.Interrupt {
return
}
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("qemu: Stopping virtual machine")
guestShutdown := true
err = guest.Shutdown(virt.Id)
if err != nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": err,
}).Warn("qemu: Failed to send shutdown to guest agent")
err = nil
guestShutdown = false
}
logged := false
for i := 0; i < 10; i++ {
err = qmp.Shutdown(virt.Id)
if err == nil {
break
}
if guestShutdown {
err = nil
break
}
if !logged {
logged = true
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": err,
}).Warn("qemu: Failed to send shutdown to virtual machine")
}
time.Sleep(500 * time.Millisecond)
}
shutdown := false
if err != nil {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
"error": err,
}).Error("qemu: Power off virtual machine error")
err = nil
} else {
for i := 0; i < settings.Hypervisor.StopTimeout; i++ {
vrt, e := GetVmInfo(db, virt.Id, false, true)
if e != nil {
err = e
return
}
if vrt == nil || vrt.State == vm.Stopped ||
vrt.State == vm.Failed {
if vrt != nil {
err = vrt.Commit(db)
if err != nil {
return
}
}
shutdown = true
break
}
time.Sleep(1 * time.Second)
if (i+1)%15 == 0 {
go func() {
qmp.Shutdown(virt.Id)
qms.Shutdown(virt.Id)
}()
}
}
}
if !shutdown {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
}).Warning("qemu: Force power off virtual machine")
err = systemd.Stop(unitName)
if err != nil {
return
}
}
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
hugepagesPath := paths.GetHugepagePath(virt.Id)
_ = os.Remove(hugepagesPath)
err = NetworkConfClear(db, virt)
if err != nil {
return
}
time.Sleep(3 * time.Second)
err = deactivateDisks(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
func ForcePowerOffErr(db *database.Database, virt *vm.VirtualMachine,
e error) (err error) {
unitName := paths.GetUnitName(virt.Id)
if constants.Interrupt {
return
}
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"error": e,
}).Error("qemu: Force power off virtual machine")
go guest.Shutdown(virt.Id)
go qmp.Shutdown(virt.Id)
go qms.Shutdown(virt.Id)
time.Sleep(15 * time.Second)
err = systemd.Stop(unitName)
if err != nil {
return
}
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
err = NetworkConfClear(db, virt)
if err != nil {
return
}
time.Sleep(3 * time.Second)
err = deactivateDisks(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
func ForcePowerOff(db *database.Database, virt *vm.VirtualMachine) (
err error) {
unitName := paths.GetUnitName(virt.Id)
if constants.Interrupt {
return
}
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
}).Warning("qemu: Force power off virtual machine")
go guest.Shutdown(virt.Id)
go qmp.Shutdown(virt.Id)
go qms.Shutdown(virt.Id)
time.Sleep(5 * time.Second)
err = systemd.Stop(unitName)
if err != nil {
return
}
_ = tpm.Stop(virt)
_ = dhcpc.Stop(virt)
_ = imds.Stop(virt)
_ = dhcps.Stop(virt)
_ = virtiofs.StopAll(virt)
err = NetworkConfClear(db, virt)
if err != nil {
return
}
time.Sleep(3 * time.Second)
err = deactivateDisks(db, virt)
if err != nil {
return
}
store.RemVirt(virt.Id)
store.RemDisks(virt.Id)
return
}
================================================
FILE: qemu/qemu.go
================================================
package qemu
import (
"crypto/md5"
"fmt"
"math"
"path"
"path/filepath"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/compositor"
"github.com/pritunl/pritunl-cloud/drive"
"github.com/pritunl/pritunl-cloud/features"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/permission"
"github.com/pritunl/pritunl-cloud/render"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
type Disk struct {
Id string
Index int
File string
Format string
}
type Network struct {
Iface string
MacAddress string
}
type Iso struct {
Name string
}
type UsbDevice struct {
Id string
Vendor string
Product string
Bus string
Address string
BusPath string
}
type PciDevice struct {
Slot string
Gpu bool
}
type DriveDevice struct {
Id string
Type string
VgName string
LvName string
}
type Mount struct {
Id string
Name string
Sock string
}
type IscsiDevice struct {
Uri string
}
type Qemu struct {
Id bson.ObjectID
Data string
Kvm bool
Machine string
Cpu string
Cpus int
Cores int
Threads int
Dies int
Sockets int
Boot string
Uefi bool
SecureBoot bool
Tpm bool
OvmfCodePath string
OvmfVarsPath string
Memory int
Hugepages bool
Vnc bool
VncDisplay int
Spice bool
SpicePort int
Gui bool
GuiUser string
GuiMode string
ProtectHome bool
ProtectTmp bool
Namespace string
Disks Disks
Networks []*Network
Isos []*Iso
UsbDevices []*UsbDevice
PciDevices []*PciDevice
DriveDevices []*DriveDevice
IscsiDevices []*IscsiDevice
Mounts []*Mount
}
func (q *Qemu) GetDiskQueues() (queues int) {
queues = int(math.Ceil(float64(q.Cores) / 2))
if queues > settings.Hypervisor.DiskQueuesMax {
queues = settings.Hypervisor.DiskQueuesMax
} else if queues < settings.Hypervisor.DiskQueuesMin {
queues = settings.Hypervisor.DiskQueuesMin
}
return
}
func (q *Qemu) GetNetworkQueues() (queues int) {
queues = int(math.Ceil(float64(q.Cores) / 2))
if queues > settings.Hypervisor.NetworkQueuesMax {
queues = settings.Hypervisor.NetworkQueuesMax
} else if queues < settings.Hypervisor.NetworkQueuesMin {
queues = settings.Hypervisor.NetworkQueuesMin
}
return
}
func (q *Qemu) GetNetworkVectors() (vectors int) {
vectors = int(math.Ceil(float64(q.Cores) / 2))
if vectors > settings.Hypervisor.NetworkQueuesMax {
vectors = settings.Hypervisor.NetworkQueuesMax
} else if vectors < settings.Hypervisor.NetworkQueuesMin {
vectors = settings.Hypervisor.NetworkQueuesMin
}
vectors = (2 * vectors) + 2
return
}
func (q *Qemu) Marshal() (output string, err error) {
localIsosPath := paths.GetLocalIsosPath()
slot := -1
qemuPath, err := features.GetQemuPath()
if err != nil {
return
}
cmd := []string{
qemuPath,
"-nographic",
}
cmd = append(cmd, "-uuid")
cmd = append(cmd, paths.GetVmUuid(q.Id))
nodeVga := node.Self.Vga
nodeVgaRenderPath := ""
if nodeVga == "" {
nodeVga = node.Virtio
}
if node.VgaRenderModes.Contains(nodeVga) {
nodeVgaRender := node.Self.VgaRender
if nodeVgaRender != "" {
nodeVgaRenderPath, err = render.GetRender(nodeVgaRender)
if err != nil {
return
}
}
}
memoryBackend, err := features.GetMemoryBackendSupport()
if err != nil {
return
}
pciPassthrough := false
gpuPassthrough := false
if node.Self.PciPassthrough && len(q.PciDevices) > 0 {
pciPassthrough = true
for i, device := range q.PciDevices {
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd,
fmt.Sprintf("pcie-root-port,id=pcibus%d,slot=%d", i, slot))
cmd = append(cmd, "-device")
if device.Gpu {
gpuPassthrough = true
cmd = append(cmd, fmt.Sprintf(
"vfio-pci,host=0000:%s,bus=pcibus%d,"+
"multifunction=on,x-vga=on",
device.Slot,
i,
))
} else {
cmd = append(cmd, fmt.Sprintf(
"vfio-pci,host=0000:%s,bus=pcibus%d",
device.Slot, i,
))
}
}
if gpuPassthrough {
cmd = append(cmd, "-display")
cmd = append(cmd, "none")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
}
}
vgaPrime := false
if !gpuPassthrough && (q.Vnc || q.Spice || q.Gui) {
if q.Gui {
cmd = append(cmd, "-display")
if q.GuiMode == node.Gtk && !settings.Hypervisor.NoGuiFullscreen {
cmd = append(cmd, fmt.Sprintf(
"%s,gl=on,window-close=off,full-screen=on", q.GuiMode))
} else {
cmd = append(cmd, fmt.Sprintf(
"%s,gl=on,window-close=off", q.GuiMode))
}
} else if node.VgaRenderModes.Contains(nodeVga) {
cmd = append(cmd, "-display")
options := "egl-headless"
if nodeVgaRenderPath != "" {
options += fmt.Sprintf(",rendernode=%s", nodeVgaRenderPath)
}
cmd = append(cmd, options)
}
switch nodeVga {
case node.Std:
cmd = append(cmd, "-vga")
cmd = append(cmd, "std")
case node.Vmware:
cmd = append(cmd, "-vga")
cmd = append(cmd, "vmware")
case node.Virtio:
cmd = append(cmd, "-vga")
cmd = append(cmd, "virtio")
case node.VirtioPci:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-pci")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioPciPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-pci")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioVgaGl:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-vga-gl")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioVgaGlPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-vga-gl")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioVgaGlVulkan:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-vga-gl,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioVgaGlVulkanPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-vga-gl,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioGl:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-gl")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioGlPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-gl")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioGlVulkan:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-gpu-gl,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioGlVulkanPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-gpu-gl,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioPciGl:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-gl-pci")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioPciGlPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-gpu-gl-pci")
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
case node.VirtioPciGlVulkan:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-gpu-gl-pci,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
case node.VirtioPciGlVulkanPrime:
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-gpu-gl-pci,blob=true,hostmem=%dM,venus=true",
settings.Hypervisor.GlHostMem,
))
cmd = append(cmd, "-vga")
cmd = append(cmd, "none")
vgaPrime = true
default:
cmd = append(cmd, "-vga")
cmd = append(cmd, nodeVga)
}
if q.Vnc {
cmd = append(cmd, "-vnc")
cmd = append(cmd, fmt.Sprintf(
":%d,websocket=%d,password=on,share=allow-exclusive",
q.VncDisplay,
q.VncDisplay+15900,
))
}
if q.Spice {
cmd = append(cmd, "-spice")
cmd = append(cmd, fmt.Sprintf(
"ipv4=on,port=%d,image-compression=off",
q.SpicePort,
))
}
}
if q.Uefi {
cmd = append(cmd, "-drive")
cmd = append(cmd, fmt.Sprintf(
"if=pflash,format=raw,unit=0,readonly=on,file=%s",
q.OvmfCodePath,
))
cmd = append(cmd, "-drive")
cmd = append(cmd, fmt.Sprintf(
"if=pflash,format=raw,unit=1,file=%s",
q.OvmfVarsPath,
))
}
if q.Kvm {
cmd = append(cmd, "-enable-kvm")
}
cmd = append(cmd, "-name")
cmd = append(cmd, fmt.Sprintf("pritunl_%s", q.Id.Hex()))
if !pciPassthrough {
supported, e := features.GetRunWithSupport()
if e != nil {
err = e
return
}
if supported {
cmd = append(cmd, "-run-with")
cmd = append(cmd, fmt.Sprintf(
"user=%s", permission.GetUserName(q.Id)))
} else {
cmd = append(cmd, "-runas")
cmd = append(cmd, permission.GetUserName(q.Id))
}
}
for i := 0; i < 10; i++ {
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd,
fmt.Sprintf("pcie-root-port,id=diskbus%d,slot=%d", i, slot))
}
cmd = append(cmd, "-machine")
options := ",mem-merge=on"
if q.SecureBoot {
options += ",smm=on"
}
if q.Hugepages && memoryBackend {
options += ",memory-backend=pc.ram"
}
if q.Kvm {
options += ",accel=kvm"
}
if gpuPassthrough || (!q.Vnc && !q.Spice && !q.Gui) {
options += ",vmport=off"
}
cmd = append(cmd, fmt.Sprintf("type=%s%s", q.Machine, options))
if q.Kvm {
cmd = append(cmd, "-cpu")
cmd = append(cmd, q.Cpu)
}
//cmd = append(cmd, "-no-hpet")
cmd = append(cmd, "-rtc", "base=utc,driftfix=slew")
cmd = append(cmd, "-msg", "timestamp=on")
cmd = append(cmd, "-global", "kvm-pit.lost_tick_policy=delay")
cmd = append(cmd, "-global", "ICH9-LPC.disable_s3=1")
cmd = append(cmd, "-global", "ICH9-LPC.disable_s4=1")
if q.SecureBoot {
cmd = append(
cmd,
"-global",
"driver=cfi.pflash01,property=secure,value=on",
)
}
cmd = append(cmd, "-smp")
cmd = append(cmd, fmt.Sprintf(
"cores=%d,threads=%d,dies=%d,sockets=%d",
q.Cores,
q.Threads,
q.Dies,
q.Sockets,
))
if q.Isos != nil && len(q.Isos) > 0 {
cmd = append(cmd, "-boot")
cmd = append(
cmd,
fmt.Sprintf(
"order=d,menu=on,splash-time=%d",
settings.Hypervisor.SplashTime*1000,
),
)
} else {
cmd = append(cmd, "-boot")
cmd = append(cmd, q.Boot)
}
cmd = append(cmd, "-m")
cmd = append(cmd, fmt.Sprintf("%dM", q.Memory))
memShare := "off"
if len(q.Mounts) > 0 {
memShare = "on"
}
if q.Hugepages {
if memoryBackend {
cmd = append(cmd, "-object")
cmd = append(cmd, fmt.Sprintf(
"memory-backend-file,id=pc.ram,"+
"size=%dM,mem-path=%s,prealloc=on,share=%s,merge=off",
q.Memory,
paths.GetHugepagePath(q.Id),
memShare,
))
} else {
cmd = append(cmd, "-mem-path")
cmd = append(cmd, paths.GetHugepagePath(q.Id))
}
}
if settings.Hypervisor.VirtRng {
cmd = append(cmd, "-object",
"rng-random,filename=/dev/random,id=rng0")
cmd = append(cmd, "-device", "virtio-rng-pci,rng=rng0")
}
if pciPassthrough {
cmd = append(cmd, "-device", "intel-iommu,intremap=on,caching-mode=on")
}
diskAio := settings.Hypervisor.DiskAio
if diskAio == "" {
supported, e := features.GetUringSupport()
if e != nil {
err = e
return
}
if supported {
diskAio = "io_uring"
} else {
diskAio = "native"
}
}
for _, disk := range q.Disks {
dskId := fmt.Sprintf("fd_%s", disk.Id)
dskFileId := fmt.Sprintf("fdf_%s", disk.Id)
dskDevId := fmt.Sprintf("fdd_%s", disk.Id)
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=file,node-name=%s,filename=%s,aio=%s,"+
"discard=unmap,cache.direct=on,cache.no-flush=off",
dskFileId,
disk.File,
diskAio,
))
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=%s,node-name=%s,file=%s,"+
"cache.direct=on,cache.no-flush=off",
disk.Format,
dskId,
dskFileId,
))
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,"+
"bus=diskbus%d,write-cache=on,packed=on",
dskId,
q.GetDiskQueues(),
dskDevId,
disk.Index,
))
}
for _, device := range q.DriveDevices {
drivePth := ""
if device.Type == vm.Lvm {
drivePth = filepath.Join("/dev/mapper",
fmt.Sprintf("%s-%s", device.VgName, device.LvName))
} else {
drivePth = paths.GetDrivePath(device.Id)
}
dskHashId := drive.GetDriveHashId(device.Id)
dskId := fmt.Sprintf("pd_%s", dskHashId)
dskFileId := fmt.Sprintf("pdf_%s", dskHashId)
dskDevId := fmt.Sprintf("pdd_%s", dskHashId)
dskBusId := fmt.Sprintf("pdb_%s", dskHashId)
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"pcie-root-port,id=%s,slot=%d",
dskBusId, slot,
))
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=file,node-name=%s,filename=%s,aio=%s,"+
"discard=unmap,cache.direct=on,cache.no-flush=off",
dskFileId,
drivePth,
diskAio,
))
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=raw,node-name=%s,file=%s,"+
"cache.direct=on,cache.no-flush=off",
dskId,
dskFileId,
))
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,"+
"bus=%s,write-cache=on,packed=on",
dskId,
q.GetDiskQueues(),
dskDevId,
dskBusId,
))
}
hasIscsi := false
if node.Self.Iscsi {
for _, device := range q.IscsiDevices {
if !hasIscsi {
cmd = append(cmd, "-iscsi")
cmd = append(cmd, fmt.Sprintf(
"initiator-name=iqn.2008-11.org.linux-kvm:%s",
q.Id.Hex(),
))
hasIscsi = true
}
iscsiHash := md5.New()
iscsiHash.Write([]byte(device.Uri))
iscsiId := fmt.Sprintf("%x", iscsiHash.Sum(nil))
dskId := fmt.Sprintf("id_%s", iscsiId)
dskFileId := fmt.Sprintf("idf_%s", iscsiId)
dskDevId := fmt.Sprintf("idd_%s", iscsiId)
dskBusId := fmt.Sprintf("idb_%s", iscsiId)
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"pcie-root-port,id=%s,slot=%d",
dskBusId, slot,
))
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=iscsi,node-name=%s,transport=tcp,"+
"url=%s,cache.direct=on",
dskFileId,
device.Uri,
))
cmd = append(cmd, "-blockdev")
cmd = append(cmd, fmt.Sprintf(
"driver=raw,node-name=%s,file=%s,"+
"cache.direct=on,cache.no-flush=off",
dskId,
dskFileId,
))
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,"+
"bus=%s,write-cache=on,packed=on",
dskId,
q.GetDiskQueues(),
dskDevId,
dskBusId,
))
}
}
for _, mount := range q.Mounts {
vfsId := fmt.Sprintf("vfs_%s", mount.Id)
vfsDevId := fmt.Sprintf("vfsd_%s", mount.Id)
vfsBusId := fmt.Sprintf("vfsb_%s", mount.Id)
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"pcie-root-port,id=%s,slot=%d",
vfsBusId, slot,
))
cmd = append(cmd, "-chardev")
cmd = append(cmd, fmt.Sprintf(
"socket,id=%s,path=%s",
vfsId,
mount.Sock,
))
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"vhost-user-fs-pci,chardev=%s,tag=\"%s\",id=%s,bus=%s",
vfsId,
mount.Name,
vfsDevId,
vfsBusId,
))
}
for i, network := range q.Networks {
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf(
"virtio-net-pci,netdev=net%d,mac=%s,mq=on,"+
"packed=on,rss=on,vectors=%d",
i,
network.MacAddress,
q.GetNetworkVectors(),
))
cmd = append(cmd, "-netdev")
cmd = append(cmd, fmt.Sprintf(
"tap,id=net%d,ifname=%s,script=no,vhost=on,queues=%d",
i,
network.Iface,
q.GetNetworkQueues(),
))
}
cmd = append(cmd, "-drive")
cmd = append(cmd, fmt.Sprintf(
"file=%s,media=cdrom,index=0",
paths.GetInitPath(q.Id),
))
if len(q.Isos) > 0 {
for i, iso := range q.Isos {
cmd = append(cmd, "-drive")
cmd = append(cmd, fmt.Sprintf(
"file=%s,media=cdrom,index=%d",
path.Join(
localIsosPath,
path.Base(utils.FilterRelPath(iso.Name)),
),
i+1,
))
}
}
cmd = append(cmd, "-monitor")
cmd = append(cmd, fmt.Sprintf(
"unix:%s,server=on,wait=off",
paths.GetSockPath(q.Id),
))
cmd = append(cmd, "-qmp")
cmd = append(cmd, fmt.Sprintf(
"unix:%s,server=on,wait=off",
paths.GetQmpSockPath(q.Id),
))
cmd = append(cmd, "-pidfile")
cmd = append(cmd, paths.GetPidPath(q.Id))
if q.Tpm {
cmd = append(cmd, "-chardev")
cmd = append(cmd, fmt.Sprintf(
"socket,id=tpmsock0,path=%s",
paths.GetTpmSockPath(q.Id),
))
cmd = append(cmd, "-tpmdev")
cmd = append(cmd, "emulator,id=tpmdev0,chardev=tpmsock0")
cmd = append(cmd, "-device")
cmd = append(cmd, "tpm-tis,tpmdev=tpmdev0")
}
guestPath := paths.GetGuestPath(q.Id)
cmd = append(cmd, "-chardev")
cmd = append(cmd, fmt.Sprintf(
"socket,path=%s,server=on,wait=off,id=guest",
guestPath,
))
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-serial")
cmd = append(cmd, "-device")
cmd = append(cmd,
"virtserialport,chardev=guest,name=org.qemu.guest_agent.0")
if !settings.Hypervisor.NoSandbox {
cmd = append(cmd, "-sandbox")
if q.Gui {
cmd = append(cmd, "on,obsolete=deny,elevateprivileges=allow,"+
"spawn=allow,resourcecontrol=deny")
} else {
cmd = append(cmd, "on,obsolete=deny,elevateprivileges=allow,"+
"spawn=deny,resourcecontrol=deny")
}
}
if q.Gui && !settings.Hypervisor.NoVirtioHid {
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-tablet-pci")
cmd = append(cmd, "-device")
cmd = append(cmd, "virtio-keyboard-pci")
}
if node.Self.UsbPassthrough || q.Vnc || q.Spice || q.Gui {
slot += 1
cmd = append(cmd, "-device")
cmd = append(cmd,
fmt.Sprintf("pcie-root-port,id=usbbus,slot=%d", slot))
cmd = append(cmd, "-device")
cmd = append(cmd, fmt.Sprintf("qemu-xhci,bus=usbbus,p2=%d,p3=%d",
settings.Hypervisor.UsbHsPorts,
settings.Hypervisor.UsbSsPorts,
))
if (q.Vnc || q.Spice) || (q.Gui && settings.Hypervisor.NoVirtioHid) {
cmd = append(cmd, "-device")
cmd = append(cmd, "usb-tablet")
cmd = append(cmd, "-device")
cmd = append(cmd, "usb-kbd")
}
for _, device := range q.UsbDevices {
cmd = append(cmd,
"-device",
fmt.Sprintf(
"usb-host,hostdevice=%s,id=%s",
device.BusPath,
device.Id,
),
)
}
}
compositorEnv := ""
if q.Gui {
compositorEnv, err = compositor.GetEnv(
q.GuiUser, nodeVgaRenderPath, vgaPrime)
if err != nil {
return
}
}
protectTmp := ""
if q.ProtectTmp {
protectTmp = "true"
} else {
protectTmp = "false"
}
protectHome := ""
if q.ProtectHome {
protectHome = "true"
} else {
protectHome = "read-only"
}
if q.Namespace == "" {
output = fmt.Sprintf(
systemdTemplateExternalNet,
q.Data,
compositorEnv,
paths.GetCacheDir(q.Id),
strings.Join(cmd, " "),
protectTmp,
protectHome,
)
} else {
output = fmt.Sprintf(
systemdTemplate,
q.Data,
compositorEnv,
paths.GetCacheDir(q.Id),
strings.Join(cmd, " "),
protectTmp,
protectHome,
q.Namespace,
)
}
return
}
================================================
FILE: qemu/routes.go
================================================
package qemu
import (
"fmt"
"net"
"strconv"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
)
func GetRoutes(instId bson.ObjectID) (icmpRedirects bool,
routes []vpc.Route, routes6 []vpc.Route, err error) {
namespace := vm.GetNamespace(instId, 0)
icmpRedirects = true
output, _ := utils.ExecCombinedOutputLogged(
nil,
"ip", "netns", "exec", namespace,
"sysctl", "net.ipv4.conf.br0.send_redirects",
)
if output != "" {
parts := strings.Split(strings.TrimSpace(output), "=")
if len(parts) == 2 {
valueStr := strings.TrimSpace(parts[1])
value, _ := strconv.Atoi(valueStr)
icmpRedirects = value == 1
}
}
output, _ = utils.ExecCombinedOutputLogged(
[]string{
"not configured in this system",
},
"ip", "netns", "exec", namespace,
"route", "-n",
)
if output == "" {
return
}
routes = []vpc.Route{}
routes6 = []vpc.Route{}
lines := strings.Split(output, "\n")
if len(lines) > 2 {
for _, line := range lines[2:] {
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) < 8 {
continue
}
if fields[4] != "97" {
continue
}
if fields[0] == "0.0.0.0" || fields[1] == "0.0.0.0" {
continue
}
mask := utils.ParseIpMask(fields[2])
if mask == nil {
continue
}
cidr, _ := mask.Size()
route := vpc.Route{
Destination: fmt.Sprintf("%s/%d", fields[0], cidr),
Target: fields[1],
}
routes = append(routes, route)
}
}
output, _ = utils.ExecCombinedOutputLogged(
[]string{
"not configured in this system",
},
"ip", "netns", "exec", namespace,
"route", "-6", "-n",
)
if output == "" {
return
}
lines = strings.Split(output, "\n")
if len(lines) > 2 {
for _, line := range lines[2:] {
if line == "" {
continue
}
fields := strings.Fields(line)
if len(fields) < 7 {
continue
}
if fields[3] != "97" {
continue
}
_, destination, e := net.ParseCIDR(fields[0])
if e != nil || destination == nil {
continue
}
target := net.ParseIP(fields[1])
if target == nil {
continue
}
route := vpc.Route{
Destination: destination.String(),
Target: target.String(),
}
routes6 = append(routes6, route)
}
}
return
}
================================================
FILE: qemu/sort.go
================================================
package qemu
type Disks []*Disk
func (d Disks) Len() int {
return len(d)
}
func (d Disks) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d Disks) Less(i, j int) bool {
return d[i].Index < d[j].Index
}
================================================
FILE: qemu/usb.go
================================================
package qemu
import (
"time"
"github.com/pritunl/pritunl-cloud/qms"
"github.com/pritunl/pritunl-cloud/store"
"github.com/pritunl/pritunl-cloud/vm"
)
func UpdateVmUsb(virt *vm.VirtualMachine) (err error) {
for i := 0; i < 10; i++ {
if virt.State == vm.Running {
usbs, e := qms.GetUsbDevices(virt.Id)
if e != nil {
if i < 9 {
time.Sleep(300 * time.Millisecond)
_ = UpdateState(virt)
continue
}
err = e
return
}
virt.UsbDevices = usbs
store.SetUsbs(virt.Id, usbs)
}
break
}
return
}
================================================
FILE: qemu/utils.go
================================================
package qemu
import (
"encoding/json"
"sort"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/pci"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
func NewQemu(virt *vm.VirtualMachine) (qm *Qemu, err error) {
data, err := json.Marshal(virt)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qemu: Failed to marshal virt"),
}
return
}
ovmfCodePath := ""
if virt.Uefi {
ovmfCodePath, err = paths.FindOvmfCodePath(virt.SecureBoot)
if err != nil {
return
}
}
guiUser := node.Self.GuiUser
guiMode := node.Self.GuiMode
if guiMode == "" {
guiMode = node.Sdl
}
namespace := ""
if !virt.HasExternalNetwork() {
namespace = vm.GetNamespace(virt.Id, 0)
}
qm = &Qemu{
Id: virt.Id,
Data: string(data),
Kvm: node.Self.Hypervisor == node.Kvm,
Machine: "q35",
Cpu: "host",
Cores: virt.Processors,
Threads: 1,
Dies: 1,
Sockets: 1,
Boot: "c",
Uefi: virt.Uefi,
SecureBoot: virt.SecureBoot,
Tpm: virt.Tpm,
OvmfCodePath: ovmfCodePath,
OvmfVarsPath: paths.GetOvmfVarsPath(virt.Id),
Memory: virt.Memory,
Hugepages: virt.Hugepages,
Vnc: virt.Vnc && virt.VncDisplay != 0,
VncDisplay: virt.VncDisplay,
Spice: virt.Spice && virt.SpicePort != 0,
SpicePort: virt.SpicePort,
Gui: virt.Gui && node.Self.Gui && guiUser != "",
GuiUser: guiUser,
GuiMode: guiMode,
ProtectHome: virt.ProtectHome(),
ProtectTmp: virt.ProtectTmp(),
Namespace: namespace,
Disks: []*Disk{},
Networks: []*Network{},
Isos: []*Iso{},
UsbDevices: []*UsbDevice{},
PciDevices: []*PciDevice{},
DriveDevices: []*DriveDevice{},
IscsiDevices: []*IscsiDevice{},
Mounts: []*Mount{},
}
for _, disk := range virt.Disks {
qm.Disks = append(qm.Disks, &Disk{
Id: disk.Id.Hex(),
Index: disk.Index,
File: disk.Path,
Format: "qcow2",
})
}
sort.Sort(qm.Disks)
for i, net := range virt.NetworkAdapters {
qm.Networks = append(qm.Networks, &Network{
MacAddress: net.MacAddress,
Iface: vm.GetIface(virt.Id, i),
})
}
for _, is := range virt.Isos {
qm.Isos = append(qm.Isos, &Iso{
Name: is.Name,
})
}
for _, device := range virt.UsbDevices {
usbDevice, _ := device.GetDevice()
if usbDevice == nil {
continue
}
qm.UsbDevices = append(qm.UsbDevices, &UsbDevice{
Id: usbDevice.GetQemuId(),
Vendor: usbDevice.Vendor,
Product: usbDevice.Product,
Bus: usbDevice.Bus,
Address: usbDevice.Address,
BusPath: usbDevice.BusPath,
})
}
for _, device := range virt.PciDevices {
dev, e := pci.GetVfio(device.Slot)
if e != nil {
err = e
return
}
if dev == nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"device_slot": device.Slot,
}).Error("qemu: Failed to find vfio device")
continue
}
name := strings.ToLower(dev.Name)
qm.PciDevices = append(qm.PciDevices, &PciDevice{
Slot: device.Slot,
Gpu: strings.Contains(name, "vga compatible") ||
strings.Contains(name, "vga controller") ||
strings.Contains(name, "graphics controller") ||
strings.Contains(name, "display controller"),
})
}
for _, device := range virt.DriveDevices {
qm.DriveDevices = append(qm.DriveDevices, &DriveDevice{
Id: device.Id,
Type: device.Type,
VgName: device.VgName,
LvName: device.LvName,
})
}
for _, device := range virt.IscsiDevices {
qm.IscsiDevices = append(qm.IscsiDevices, &IscsiDevice{
Uri: device.Uri,
})
}
for _, mount := range virt.Mounts {
shareId := paths.GetShareId(virt.Id, mount.Name)
sockPath := paths.GetShareSockPath(virt.Id, shareId)
qm.Mounts = append(qm.Mounts, &Mount{
Id: shareId,
Name: utils.FilterNameCmd(mount.Name),
Sock: sockPath,
})
}
return
}
================================================
FILE: qga/qga.go
================================================
package qga
import (
"bytes"
"encoding/json"
"net"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type Command struct {
Execute string `json:"execute"`
}
type Address struct {
Type string `json:"ip-address-type"`
Address string `json:"ip-address"`
Prefix int `json:"prefix"`
}
type Interface struct {
Name string `json:"name"`
MacAddress string `json:"hardware-address"`
Addresses []*Address `json:"ip-addresses"`
}
type Interfaces struct {
Interfaces []*Interface `json:"return"`
}
func (i *Interfaces) GetAddr(macAddr string) (guestAddr, guestAddr6 string) {
macAddr = strings.ToLower(macAddr)
if i.Interfaces != nil {
for _, iface := range i.Interfaces {
if strings.ToLower(iface.MacAddress) != macAddr {
continue
}
if iface.Addresses != nil {
for _, addr := range iface.Addresses {
if addr.Type == "ipv4" && guestAddr == "" {
guestAddr = addr.Address
} else if addr.Type == "ipv6" && guestAddr6 == "" {
ipAddr := strings.ToLower(addr.Address)
if !strings.HasPrefix(ipAddr, "fe") {
guestAddr6 = strings.ToLower(addr.Address)
}
}
}
}
break
}
}
return
}
func GetInterfaces(sockPath string) (ifaces *Interfaces, err error) {
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ConnectionError{
errors.Wrap(err, "qga: Failed to connect to guest agent"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
return
}
cmd := &Command{
Execute: "guest-network-get-interfaces",
}
cmdByte, err := json.Marshal(cmd)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qga: Failed to parse guest agent command"),
}
return
}
_, err = conn.Write(cmdByte)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "qga: Failed to write to guest agent"),
}
return
}
buffer := make([]byte, 5000000)
n, err := conn.Read(buffer)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "qga: Failed to read from guest agent"),
}
return
}
buffer = buffer[:n]
respByt := bytes.Trim(buffer, "\x00")
respByt = bytes.TrimSpace(respByt)
ifaces = &Interfaces{}
err = json.Unmarshal(respByt, ifaces)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qga: Failed to parse guest agent response"),
}
return
}
return
}
================================================
FILE: qmp/backup.go
================================================
package qmp
import (
"path"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
type driveBackupArgs struct {
Device string `json:"device"`
Sync string `json:"sync"`
Target string `json:"target"`
Format string `json:"format"`
}
type blockDeviceImage struct {
Filename string `json:"filename"`
}
type blockDeviceInserted struct {
Image blockDeviceImage `json:"image"`
}
type blockDevice struct {
Device string `json:"device"`
Inserted blockDeviceInserted `json:"inserted"`
}
type blockDeviceReturn struct {
Return []*blockDevice `json:"return"`
Error *CommandError `json:"error"`
}
func driveGetDevice(vmId bson.ObjectID, dsk *disk.Disk) (
name string, err error) {
cmd := &Command{
Execute: "query-block",
}
returnData := &blockDeviceReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
if returnData.Return == nil {
err = &errortypes.ParseError{
errors.Newf("qmp: Return nil"),
}
return
}
for _, blockDev := range returnData.Return {
idStr := strings.Split(path.Base(
blockDev.Inserted.Image.Filename), ".")[0]
diskId, err := bson.ObjectIDFromHex(idStr)
if err != nil {
continue
}
if diskId == dsk.Id {
name = blockDev.Device
break
}
}
return
}
func driveBackup(vmId bson.ObjectID, dsk *disk.Disk,
destPth string) (deviceName string, err error) {
deviceName, err = driveGetDevice(vmId, dsk)
if err != nil {
return
}
if deviceName == "" {
err = &DiskNotFound{
errors.Newf("qmp: Disk not found %s", dsk.Id.Hex()),
}
return
}
cmd := &Command{
Execute: "drive-backup",
Arguments: &driveBackupArgs{
Device: deviceName,
Sync: "full",
Target: destPth,
Format: "qcow2",
},
}
returnData := &CommandReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
time.Sleep(1 * time.Second)
return
}
func driveBackupCheck(vmId bson.ObjectID, deviceName string) (
complete bool, err error) {
cmd := &Command{
Execute: "query-jobs",
}
returnData := &JobStatusReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
if returnData.Return == nil {
err = &errortypes.ParseError{
errors.Newf("qmp: Return nil"),
}
return
}
for _, status := range returnData.Return {
if status.Type == "backup" &&
status.Id == deviceName &&
status.Status != "concluded" {
return
}
}
complete = true
return
}
func BackupDisk(vmId bson.ObjectID, dsk *disk.Disk,
destPth string) (err error) {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_id": dsk.Id.Hex(),
}).Info("qmp: Backing up disk")
deviceName, err := driveBackup(vmId, dsk, destPth)
if err != nil {
return
}
for {
complete, e := driveBackupCheck(vmId, deviceName)
if e != nil {
err = e
return
}
if complete {
break
}
time.Sleep(3 * time.Second)
}
return
}
================================================
FILE: qmp/disk.go
================================================
package qmp
import (
"fmt"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/features"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
type blockDevFileArgs struct {
Driver string `json:"driver"`
NodeName string `json:"node-name"`
Aio string `json:"aio"`
Discard string `json:"discard"`
Filename string `json:"filename"`
Cache blockDevCache `json:"cache"`
}
type blockDevArgs struct {
Driver string `json:"driver"`
NodeName string `json:"node-name"`
File string `json:"file"`
Cache blockDevCache `json:"cache"`
}
type blockDevCache struct {
NoFlush bool `json:"no-flush"`
Direct bool `json:"direct"`
}
type deviceAddArgs struct {
Id string `json:"id"`
Driver string `json:"driver"`
Drive string `json:"drive"`
Bus string `json:"bus"`
}
type blockDevEventData struct {
Device string `json:"device"`
Path string `json:"path"`
}
type blockDevEvent struct {
Event string `json:"event"`
Data blockDevEventData `json:"data"`
}
func AddDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {
dskId := fmt.Sprintf("fd_%s", dsk.Id.Hex())
dskFileId := fmt.Sprintf("fdf_%s", dsk.Id.Hex())
dskDevId := fmt.Sprintf("fdd_%s", dsk.Id.Hex())
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_id": dsk.Id.Hex(),
"disk_index": dsk.Index,
}).Info("qmp: Connecting virtual disk")
diskAio := settings.Hypervisor.DiskAio
if diskAio == "" {
supported, e := features.GetUringSupport()
if e != nil {
err = e
return
}
if supported {
diskAio = "io_uring"
} else {
diskAio = "threads"
}
}
conn := NewConnection(vmId, true)
defer conn.Close()
_, err = conn.Connect()
if err != nil {
return
}
cmd := &Command{
Execute: "blockdev-add",
Arguments: &blockDevFileArgs{
Driver: "file",
NodeName: dskFileId,
Aio: diskAio,
Discard: "unmap",
Filename: dsk.Path,
Cache: blockDevCache{
NoFlush: false,
Direct: true,
},
},
}
returnData := &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil &&
!strings.Contains(
strings.ToLower(returnData.Error.Desc),
"duplicate",
) {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
cmd = &Command{
Execute: "blockdev-add",
Arguments: &blockDevArgs{
Driver: "qcow2",
NodeName: dskId,
File: dskFileId,
Cache: blockDevCache{
NoFlush: false,
Direct: true,
},
},
}
returnData = &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil &&
!strings.Contains(
strings.ToLower(returnData.Error.Desc),
"duplicate",
) {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
cmd = &Command{
Execute: "device_add",
Arguments: &deviceAddArgs{
Id: dskDevId,
Driver: "virtio-blk-pci",
Drive: dskId,
Bus: fmt.Sprintf("diskbus%d", dsk.Index),
},
}
returnData = &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
time.Sleep(1 * time.Second)
return
}
func RemoveDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {
dskId := fmt.Sprintf("fd_%s", dsk.Id.Hex())
dskFileId := fmt.Sprintf("fdf_%s", dsk.Id.Hex())
dskDevId := fmt.Sprintf("fdd_%s", dsk.Id.Hex())
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_id": dsk.Id.Hex(),
"disk_index": dsk.Index,
}).Info("qmp: Disconnecting virtual disk")
diskAio := settings.Hypervisor.DiskAio
if diskAio == "" {
supported, e := features.GetUringSupport()
if e != nil {
err = e
return
}
if supported {
diskAio = "io_uring"
} else {
diskAio = "threads"
}
}
conn := NewConnection(vmId, true)
defer conn.Close()
conn.SetDeadline(30 * time.Second)
_, err = conn.Connect()
if err != nil {
return
}
cmd := &Command{
Execute: "device_del",
Arguments: &CommandId{
Id: dskDevId,
},
}
returnData := &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
skipEvent := false
if returnData.Error != nil && (strings.Contains(
strings.ToLower(returnData.Error.Desc),
"process of unplug") || strings.Contains(
strings.ToLower(returnData.Error.Desc),
"not found") || strings.Contains(
strings.ToLower(returnData.Error.Desc),
"failed to find")) {
skipEvent = true
} else if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
if !skipEvent {
event := &blockDevEvent{}
err = conn.Event(event, func() (resp interface{}, err error) {
if event.Event == "DEVICE_DELETED" &&
event.Data.Device == dskDevId {
return
}
event = &blockDevEvent{}
resp = event
return
})
if err != nil {
return
}
}
cmd = &Command{
Execute: "blockdev-del",
Arguments: &CommandNode{
NodeName: dskId,
},
}
returnData = &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"process of unplug") && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"not found") && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"failed to find") {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
cmd = &Command{
Execute: "blockdev-del",
Arguments: &CommandNode{
NodeName: dskFileId,
},
}
returnData = &CommandReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"process of unplug") && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"not found") && !strings.Contains(
strings.ToLower(returnData.Error.Desc),
"failed to find") {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
return
}
type blockQueryReturn struct {
Return []blockQueryDevice `json:"return"`
Error *CommandError `json:"error"`
}
type blockQueryDevice struct {
Device string `json:"device"`
Locked bool `json:"locked"`
Removable bool `json:"removable"`
Inserted blockQueryInserted `json:"inserted"`
}
type blockQueryInserted struct {
NodeName string `json:"node-name"`
Drv string `json:"drv"`
File string `json:"file"`
Cache blockQueryCache `json:"cache"`
Image blockQueryImage `json:"image"`
}
type blockQueryCache struct {
NoFlush bool `json:"no-flush"`
Direct bool `json:"direct"`
Writeback bool `json:"writeback"`
}
type blockQueryImage struct {
VirtualSize int64 `json:"virtual-size"`
Filename string `json:"filename"`
Format string `json:"format"`
ActualSize int64 `json:"actual-size"`
}
type pciQueryReturn struct {
Return []pciQueryBus `json:"return"`
Error *CommandError `json:"error"`
}
type pciQueryBus struct {
Bus int `json:"bus"`
Slot int `json:"slot"`
QdevId string `json:"qdev_id"`
Devices []pciQueryBus `json:"devices,omitempty"`
PciBridge pciQueryBridge `json:"pci_bridge,omitempty"`
}
type pciQueryBridge struct {
Devices []pciQueryDevice `json:"devices,omitempty"`
}
type pciQueryDevice struct {
Bus int `json:"bus"`
Slot int `json:"slot"`
QdevId string `json:"qdev_id"`
}
func GetDisks(vmId bson.ObjectID) (info *QemuInfo, disks []*vm.Disk,
err error) {
conn := NewConnection(vmId, false)
defer conn.Close()
info, err = conn.Connect()
if err != nil {
return
}
cmd := &Command{
Execute: "query-block",
}
returnData := &blockQueryReturn{}
err = conn.Send(cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
disksMap := map[bson.ObjectID]*vm.Disk{}
index := 0
for _, disk := range returnData.Return {
var idSpl []string
if strings.HasPrefix(disk.Device, "disk_") {
idSpl = strings.Split(disk.Device, "_")
} else if strings.HasPrefix(disk.Inserted.NodeName, "fd_") {
idSpl = strings.Split(disk.Inserted.NodeName, "_")
} else {
continue
}
if len(idSpl) < 2 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"qmp_names": idSpl,
"qmp_device": disk.Device,
"qmp_node_name": disk.Inserted.NodeName,
"qmp_file": disk.Inserted.File,
"qmp_filename": disk.Inserted.Image.Filename,
}).Error("qmp: Disk id invalid")
continue
}
dskId, ok := utils.ParseObjectId(idSpl[1])
if !ok {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"qmp_names": idSpl,
"qmp_device": disk.Device,
"qmp_node_name": disk.Inserted.NodeName,
"qmp_file": disk.Inserted.File,
"qmp_filename": disk.Inserted.Image.Filename,
}).Error("qmp: Disk id parse failed")
continue
}
filename := disk.Inserted.Image.Filename
if filename == "" {
filename = disk.Inserted.File
if filename == "" {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"qmp_names": idSpl,
"qmp_device": disk.Device,
"qmp_node_name": disk.Inserted.NodeName,
"qmp_file": disk.Inserted.File,
"qmp_filename": disk.Inserted.Image.Filename,
}).Error("qmp: Disk filename invalid")
continue
}
}
dsk := &vm.Disk{
Id: dskId,
Index: index,
Path: filename,
}
disks = append(disks, dsk)
disksMap[dsk.Id] = dsk
index += 1
}
cmd = &Command{
Execute: "query-pci",
}
pciReturnData := &pciQueryReturn{}
err = conn.Send(cmd, pciReturnData)
if err != nil {
return
}
if pciReturnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", pciReturnData.Error.Desc),
}
return
}
for _, rootBus := range pciReturnData.Return {
if rootBus.Devices == nil {
continue
}
for _, subBus := range rootBus.Devices {
if !strings.HasPrefix(subBus.QdevId, "diskbus") ||
subBus.PciBridge.Devices == nil {
continue
}
for _, device := range subBus.PciBridge.Devices {
if !strings.HasPrefix(device.QdevId, "fdd_") {
continue
}
dskIndex, e := strconv.Atoi(subBus.QdevId[7:])
if e != nil {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"qmp_diskbus": subBus.QdevId,
"qmp_device": device.QdevId,
}).Error("qmp: Disk bus parse failed")
continue
}
dskId, ok := utils.ParseObjectId(device.QdevId[4:])
if !ok {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"qmp_diskbus": subBus.QdevId,
"qmp_device": device.QdevId,
}).Error("qmp: Disk bus id parse failed")
continue
}
dsk := disksMap[dskId]
if dsk == nil {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_id": dskId.Hex(),
"qmp_diskbus": subBus.QdevId,
"qmp_device": device.QdevId,
}).Error("qmp: Unknown disk found")
continue
}
dsk.Index = dskIndex
}
}
}
return
}
================================================
FILE: qmp/errors.go
================================================
package qmp
import "github.com/dropbox/godropbox/errors"
type DiskNotFound struct {
errors.DropboxError
}
================================================
FILE: qmp/password.go
================================================
package qmp
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
const (
Spice = "spice"
Vnc = "vnc"
)
type setPasswordArgs struct {
Protocol string `json:"protocol"`
Password string `json:"password"`
}
func SetPassword(vmId bson.ObjectID, proto, passwd string) (err error) {
cmd := &Command{
Execute: "set_password",
Arguments: &setPasswordArgs{
Protocol: proto,
Password: passwd,
},
}
returnData := &CommandReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
time.Sleep(50 * time.Millisecond)
return
}
================================================
FILE: qmp/power.go
================================================
package qmp
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func Shutdown(vmId bson.ObjectID) (err error) {
cmd := &Command{
Execute: "system_powerdown",
}
returnData := &CommandReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
time.Sleep(1000 * time.Millisecond)
return
}
================================================
FILE: qmp/qmp.go
================================================
package qmp
import (
"bytes"
"encoding/json"
"fmt"
"net"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/utils"
)
type Command struct {
Execute string `json:"execute"`
Arguments interface{} `json:"arguments,omitempty"`
}
type CommandId struct {
Id string `json:"id"`
}
type CommandNode struct {
NodeName string `json:"node-name"`
}
type JobStatus struct {
Id string `json:"id"`
Type string `json:"type"`
Status string `json:"status"`
}
type JobStatusReturn struct {
Return []*JobStatus `json:"return"`
Error *CommandError `json:"error"`
}
type QmpVersionData struct {
Major int `json:"major"`
Minor int `json:"minor"`
Micro int `json:"micro"`
}
type QmpVersion struct {
Qemu QmpVersionData `json:"qemu"`
}
type QmpData struct {
Version QmpVersion `json:"version"`
}
type QmpCapabilities struct {
QMP QmpData `json:"QMP"`
}
type QemuInfo struct {
VersionMajor int
VersionMinor int
VersionMicro int
}
type CommandError struct {
Class string `json:"class"`
Desc string `json:"desc"`
}
type CommandReturn struct {
Return interface{} `json:"return"`
Error *CommandError `json:"error"`
}
type EventCallback func() (resp interface{}, err error)
var (
socketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)
)
type Connection struct {
vmId bson.ObjectID
sock net.Conn
lockId bson.ObjectID
deadline time.Duration
logging bool
command interface{}
response interface{}
}
func (c *Connection) connect() (info *QemuInfo, err error) {
// TODO Backward compatibility
sockPath := paths.GetQmpSockPath(c.vmId)
sockPathOld := paths.GetQmpSockPathOld(c.vmId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if !exists {
sockPath = sockPathOld
}
c.lockId = socketsLock.Lock(c.vmId.Hex())
c.sock, err = net.DialTimeout(
"unix",
sockPath,
10*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qmp: Failed to open socket"),
}
return
}
deadline := c.deadline
if deadline == 0 {
deadline = 6 * time.Second
}
err = c.sock.SetDeadline(time.Now().Add(deadline))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qmp: Failed set deadline"),
}
return
}
var infoByt []byte
for {
buffer := make([]byte, 5000000)
n, e := c.sock.Read(buffer)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qmp: Failed to read socket"),
}
return
}
buffer = buffer[:n]
lines := bytes.Split(buffer, []byte("\n"))
for _, line := range lines {
if !constants.Production && c.logging {
fmt.Println(string(line))
}
if bytes.Contains(line, []byte(`"QMP"`)) {
infoByt = line
break
}
}
if infoByt != nil {
break
}
}
if infoByt == nil {
err = &errortypes.ReadError{
errors.New("qmp: No info message from socket"),
}
return
}
connInfo := &QmpCapabilities{}
err = json.Unmarshal(infoByt, connInfo)
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qmp: Failed to unmarshal info '%s'",
string(infoByt),
),
}
return
}
info = &QemuInfo{
VersionMajor: connInfo.QMP.Version.Qemu.Major,
VersionMinor: connInfo.QMP.Version.Qemu.Minor,
VersionMicro: connInfo.QMP.Version.Qemu.Micro,
}
return
}
func (c *Connection) Close() {
sock := c.sock
if sock != nil {
_ = sock.Close()
}
socketsLock.Unlock(c.vmId.Hex(), c.lockId)
}
func (c *Connection) SetDeadline(deadline time.Duration) {
c.deadline = deadline
}
func (c *Connection) Send(command interface{}, resp interface{}) (
err error) {
cmdData, err := json.Marshal(command)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "qmp: Failed to marshal command"),
}
return
}
if !constants.Production && c.logging {
fmt.Println(string(cmdData))
}
cmdData = append(cmdData, '\n')
_, err = c.sock.Write(cmdData)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qmp: Failed to write socket"),
}
return
}
var returnData []byte
returnWait := make(chan bool, 2)
go func() {
defer func() {
returnWait <- true
}()
for {
buffer := make([]byte, 5000000)
n, e := c.sock.Read(buffer)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qmp: Failed to read socket"),
}
return
}
buffer = buffer[:n]
lines := bytes.Split(buffer, []byte("\n"))
for _, line := range lines {
if !constants.Production && c.logging {
fmt.Println(string(line))
}
if bytes.Contains(line, []byte(`"return"`)) ||
bytes.Contains(line, []byte(`"error"`)) {
returnData = line
returnWait <- true
return
}
}
}
}()
<-returnWait
if err != nil {
return
}
if returnData == nil {
err = &errortypes.ReadError{
errors.New("qmp: No data from socket"),
}
return
}
err = json.Unmarshal(returnData, resp)
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qmp: Failed to unmarshal return '%s'",
string(returnData),
),
}
return
}
return
}
func (c *Connection) Event(resp interface{}, callback EventCallback) (
err error) {
for {
buffer := make([]byte, 5000000)
n, e := c.sock.Read(buffer)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qmp: Failed to read socket"),
}
return
}
buffer = buffer[:n]
lines := bytes.Split(buffer, []byte("\n"))
for _, line := range lines {
if !constants.Production && c.logging {
fmt.Println(string(line))
}
if bytes.Contains(line, []byte(`"event"`)) {
err = json.Unmarshal(line, resp)
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"qmp: Failed to unmarshal return '%s'",
string(line),
),
}
return
}
resp, err = callback()
if err != nil || resp == nil {
return
}
}
}
}
return
}
func (c *Connection) Connect() (info *QemuInfo, err error) {
info, err = c.connect()
if err != nil {
return
}
initCmd := &Command{
Execute: "qmp_capabilities",
}
initResp := &CommandReturn{}
err = c.Send(initCmd, initResp)
if err != nil {
return
}
if initResp.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error '%s'", initResp.Error.Desc),
}
return
}
return
}
func NewConnection(vmId bson.ObjectID, logging bool) (conn *Connection) {
conn = &Connection{
vmId: vmId,
logging: logging,
}
return
}
func RunCommand(vmId bson.ObjectID, cmd interface{},
resp interface{}) (err error) {
conn := NewConnection(vmId, true)
defer conn.Close()
_, err = conn.Connect()
if err != nil {
return
}
err = conn.Send(cmd, resp)
if err != nil {
return
}
return
}
================================================
FILE: qmp/vnc.go
================================================
package qmp
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type vncPasswordArgs struct {
Password string `json:"password"`
}
func VncPassword(vmId bson.ObjectID, passwd string) (err error) {
cmd := &Command{
Execute: "change-vnc-password",
Arguments: &vncPasswordArgs{
Password: passwd,
},
}
returnData := &CommandReturn{}
err = RunCommand(vmId, cmd, returnData)
if err != nil {
return
}
if returnData.Error != nil {
err = &errortypes.ApiError{
errors.Newf("qmp: Return error %s", returnData.Error.Desc),
}
return
}
time.Sleep(1 * time.Second)
return
}
================================================
FILE: qms/disk.go
================================================
package qms
import (
"bytes"
"fmt"
"net"
"path"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
func GetDisks(vmId bson.ObjectID) (disks []*vm.Disk, err error) {
disks = []*vm.Disk{}
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(3 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
buffer := []byte{}
for {
buf := make([]byte, 5000000)
n, e := conn.Read(buf)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to read socket"),
}
return
}
buffer = append(buffer, buf[:n]...)
if bytes.Contains(bytes.TrimSpace(buffer), []byte("(qemu)")) {
break
}
}
_, err = conn.Write([]byte("info block\n"))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
buffer = []byte{}
for {
buf := make([]byte, 5000000)
n, e := conn.Read(buf)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to read socket"),
}
return
}
buffer = append(buffer, buf[:n]...)
if bytes.Contains(bytes.TrimSpace(buffer), []byte("(qemu)")) {
break
}
}
index := 0
for _, line := range strings.Split(string(buffer), "\n") {
if len(line) < 10 {
continue
}
// TODO Backwards compatibility
if strings.HasPrefix(line, "virtio") {
line = strings.Replace(line, "\r", "", -1)
if !strings.HasPrefix(line, "virtio") || len(line) < 10 {
continue
}
line = strings.Replace(line, "\r", "", -1)
lineSpl := strings.SplitN(line[6:], ":", 2)
if len(lineSpl) != 2 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk path")
continue
}
indexStr := strings.Fields(strings.TrimSpace(lineSpl[0]))[0]
indx, e := strconv.Atoi(indexStr)
if e != nil {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk path index")
continue
}
diskPath := strings.Fields(strings.TrimSpace(lineSpl[1]))[0]
idStr := strings.Split(path.Base(diskPath), ".")[0]
diskId, err := bson.ObjectIDFromHex(idStr)
if err != nil {
continue
}
dsk := &vm.Disk{
Id: diskId,
Index: indx,
Path: diskPath,
}
disks = append(disks, dsk)
continue
}
if !strings.HasPrefix(line, "disk_") &&
!strings.HasPrefix(line, "fd_") {
continue
}
line = strings.Replace(line, "\r", "", -1)
lineSpl := strings.SplitN(line, ":", 2)
if len(lineSpl) != 2 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk id")
continue
}
idIndexStr := strings.Fields(strings.TrimSpace(lineSpl[0]))[0]
if len(idIndexStr) < 6 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk id length")
continue
}
idIndexStrSpl := strings.Split(idIndexStr, "_")
if len(idIndexStrSpl) < 2 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk id invalid")
continue
}
dskId, ok := utils.ParseObjectId(idIndexStrSpl[1])
if !ok {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu disk id parse")
continue
}
diskPath := strings.Fields(strings.TrimSpace(lineSpl[1]))[0]
dsk := &vm.Disk{
Id: dskId,
Index: index,
Path: diskPath,
}
disks = append(disks, dsk)
index += 1
}
return
}
func AddDisk(vmId bson.ObjectID, dsk *vm.Disk,
virt *vm.VirtualMachine) (err error) {
dskId := fmt.Sprintf("disk_%s", dsk.Id.Hex())
dskDevId := fmt.Sprintf("diskdev_%s", dsk.Id.Hex())
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_path": dsk.Path,
}).Info("qemu: Connecting virtual machine disk")
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
drive := fmt.Sprintf(
"file=%s,media=disk,format=qcow2,cache=none,"+
"discard=unmap,if=none,id=%s",
dsk.Path,
dskId,
)
_, err = conn.Write([]byte(fmt.Sprintf(
"drive_add 0 %s\n", drive,
)))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
queues := virt.Processors / 2
if queues > settings.Hypervisor.DiskQueuesMax {
queues = settings.Hypervisor.DiskQueuesMax
} else if queues < settings.Hypervisor.DiskQueuesMin {
queues = settings.Hypervisor.DiskQueuesMin
}
device := fmt.Sprintf(
"virtio-blk-pci,drive=%s,num-queues=%d,id=%s,bus=diskbus%d",
dskId,
queues,
dskDevId,
dsk.Index,
)
_, err = conn.Write([]byte(fmt.Sprintf(
"device_add %s\n", device,
)))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(1 * time.Second)
return
}
func RemoveDisk(vmId bson.ObjectID, dsk *vm.Disk) (err error) {
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"disk_path": dsk.Path,
}).Info("qemu: Disconnecting virtual machine disk")
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
_, err = conn.Write([]byte(
fmt.Sprintf("device_del diskdev_%s\n", dsk.Id.Hex())))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(50 * time.Millisecond)
_, err = conn.Write([]byte(
fmt.Sprintf("drive_del disk_%s\n", dsk.Id.Hex())))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(1 * time.Second)
return
}
================================================
FILE: qms/power.go
================================================
package qms
import (
"net"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func Shutdown(vmId bson.ObjectID) (err error) {
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
_, err = conn.Write([]byte("system_powerdown\n"))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(500 * time.Millisecond)
return
}
================================================
FILE: qms/qms.go
================================================
package qms
import (
"time"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
socketsLock = utils.NewMultiTimeoutLock(1 * time.Minute)
)
================================================
FILE: qms/usb.go
================================================
package qms
import (
"bytes"
"fmt"
"net"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/permission"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
func GetUsbDevices(vmId bson.ObjectID) (
devices []*vm.UsbDevice, err error) {
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(3 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
buffer := []byte{}
for {
buf := make([]byte, 5000000)
n, e := conn.Read(buf)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to read socket"),
}
return
}
buffer = append(buffer, buf[:n]...)
if bytes.Contains(bytes.TrimSpace(buffer), []byte("(qemu)")) {
break
}
}
_, err = conn.Write([]byte("info usb\n"))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
buffer = []byte{}
for {
buf := make([]byte, 5000000)
n, e := conn.Read(buf)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "qemu: Failed to read socket"),
}
return
}
buffer = append(buffer, buf[:n]...)
if bytes.Contains(bytes.TrimSpace(buffer), []byte("(qemu)")) {
break
}
}
for _, line := range strings.Split(string(buffer), "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "Device") || len(line) < 10 {
continue
}
line = strings.Replace(line, "\r", "", -1)
if !strings.Contains(line, "ID:") {
continue
}
lineSpl := strings.Split(line, "ID:")
if len(lineSpl) != 2 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu usb info")
continue
}
deviceId := strings.Fields(lineSpl[1])[0]
if strings.HasPrefix(deviceId, "usb_") {
lineSpl = strings.Split(deviceId, "_")
if len(lineSpl) != 5 && len(lineSpl) != 6 {
logrus.WithFields(logrus.Fields{
"instance_id": vmId.Hex(),
"line": line,
}).Error("qemu: Unexpected qemu usb id")
continue
}
device := &vm.UsbDevice{
Id: deviceId,
Bus: lineSpl[1],
Address: lineSpl[2],
Vendor: lineSpl[3],
Product: lineSpl[4],
}
devices = append(devices, device)
}
}
return
}
func AddUsb(virt *vm.VirtualMachine, device *vm.UsbDevice) (err error) {
sockPath, err := GetSockPath(virt.Id)
if err != nil {
return
}
usbDevice, err := device.GetDevice()
if err != nil {
return
}
if usbDevice == nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"usb_vendor": device.Vendor,
"usb_product": device.Product,
"usb_bus": device.Bus,
"usb_address": device.Address,
}).Warn("qemu: Failed to find usb device for attachment")
return
}
if usbDevice.Bus == "" || usbDevice.Address == "" ||
usbDevice.Vendor == "" || usbDevice.Product == "" {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"usb_name": usbDevice.Name,
"usb_vendor": usbDevice.Vendor,
"usb_product": usbDevice.Product,
"usb_bus": usbDevice.Bus,
"usb_address": usbDevice.Address,
"usb_path": usbDevice.BusPath,
}).Warn("qemu: Failed to load usb device info for attachment")
return
}
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"usb_name": usbDevice.Name,
"usb_vendor": usbDevice.Vendor,
"usb_product": usbDevice.Product,
"usb_bus": usbDevice.Bus,
"usb_address": usbDevice.Address,
"usb_path": usbDevice.BusPath,
}).Info("qemu: Connecting virtual machine usb")
err = permission.Chown(virt, usbDevice.BusPath)
if err != nil {
return
}
lockId := socketsLock.Lock(virt.Id.Hex())
defer socketsLock.Unlock(virt.Id.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
deviceLine := fmt.Sprintf(
"usb-host,hostdevice=%s,id=%s",
usbDevice.BusPath,
usbDevice.GetQemuId(),
)
_, err = conn.Write([]byte(
fmt.Sprintf("device_add %s\n", deviceLine)))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(1 * time.Second)
return
}
func RemoveUsb(virt *vm.VirtualMachine, device *vm.UsbDevice) (err error) {
sockPath, err := GetSockPath(virt.Id)
if err != nil {
return
}
usbDevice, err := device.GetDevice()
if err != nil {
return
}
if usbDevice != nil {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"usb_id": device.Id,
"usb_name": usbDevice.Name,
"usb_vendor": usbDevice.Vendor,
"usb_product": usbDevice.Product,
"usb_bus": usbDevice.Bus,
"usb_address": usbDevice.Address,
"usb_path": usbDevice.BusPath,
}).Info("qemu: Disconnecting active usb device")
} else {
logrus.WithFields(logrus.Fields{
"instance_id": virt.Id.Hex(),
"usb_id": device.Id,
"usb_vendor": device.Vendor,
"usb_product": device.Product,
"usb_bus": device.Bus,
"usb_address": device.Address,
}).Info("qemu: Disconnecting inactive usb device")
}
lockId := socketsLock.Lock(virt.Id.Hex())
defer socketsLock.Unlock(virt.Id.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
if device.Id != "" {
_, err = conn.Write([]byte(
fmt.Sprintf("device_del %s\n", device.Id)))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
}
time.Sleep(1 * time.Second)
if usbDevice != nil && usbDevice.BusPath != "" {
err = permission.Restore(usbDevice.BusPath)
if err != nil {
return
}
}
return
}
================================================
FILE: qms/utils.go
================================================
package qms
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/utils"
)
// TODO Backward compatibility
func GetSockPath(virtId bson.ObjectID) (pth string, err error) {
sockPath := paths.GetSockPath(virtId)
sockPathOld := paths.GetSockPathOld(virtId)
exists, err := utils.Exists(sockPath)
if err != nil {
return
}
if exists {
pth = sockPath
} else {
pth = sockPathOld
}
return
}
================================================
FILE: qms/vnc.go
================================================
package qms
import (
"fmt"
"net"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func VncPassword(vmId bson.ObjectID, passwd string) (err error) {
sockPath, err := GetSockPath(vmId)
if err != nil {
return
}
lockId := socketsLock.Lock(vmId.Hex())
defer socketsLock.Unlock(vmId.Hex(), lockId)
conn, err := net.DialTimeout(
"unix",
sockPath,
3*time.Second,
)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to open socket"),
}
return
}
defer conn.Close()
err = conn.SetDeadline(time.Now().Add(5 * time.Second))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed set deadline"),
}
return
}
_, err = conn.Write(
[]byte(fmt.Sprintf("change vnc password\n%s\n", passwd)))
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "qemu: Failed to write socket"),
}
return
}
time.Sleep(800 * time.Millisecond)
return
}
================================================
FILE: redirect/acme.go
================================================
package main
import (
"sync"
"time"
)
type Challenge struct {
Token string `json:"token"`
Response string `json:"response"`
}
var (
challenges = map[string]*Challenge{}
challengesLock = sync.Mutex{}
clearTimer *time.Timer
timerLock = sync.Mutex{}
)
func AddChallenge(chal *Challenge) {
timerLock.Lock()
if clearTimer != nil {
clearTimer.Stop()
clearTimer = nil
}
clearTimer = time.AfterFunc(60*time.Second, func() {
challengesLock.Lock()
challenges = map[string]*Challenge{}
challengesLock.Unlock()
timerLock.Lock()
clearTimer = nil
timerLock.Unlock()
})
timerLock.Unlock()
challengesLock.Lock()
challenges[chal.Token] = chal
challengesLock.Unlock()
}
func GetChallenge(token string) (chal *Challenge) {
challengesLock.Lock()
chal = challenges[token]
challengesLock.Unlock()
return
}
================================================
FILE: redirect/crypto/crypto.go
================================================
package crypto
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"encoding/json"
"io"
"github.com/pritunl/tools/errors"
"github.com/pritunl/tools/errortypes"
"golang.org/x/crypto/nacl/secretbox"
"golang.org/x/crypto/nacl/sign"
)
type Message struct {
Nonce string
Message string
Signature string
}
type AsymNaclHmacKey struct {
Key string
Secret string
PublicKey string
PrivateKey string
}
type AsymNaclHmac struct {
key *[32]byte
secret *[32]byte
publicKey *[32]byte
privateKey *[64]byte
nonceHandler func(nonce []byte) error
}
func (a *AsymNaclHmac) RegisterNonce(handler func(nonce []byte) error) {
a.nonceHandler = handler
}
func (a *AsymNaclHmac) Seal(input any) (msg *Message, err error) {
if a.key == nil || a.secret == nil || a.privateKey == nil {
err = &errortypes.AuthenticationError{
errors.New("crypto: Private key and secret not loaded"),
}
return
}
nonce := new([24]byte)
_, err = io.ReadFull(rand.Reader, nonce[:])
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to generate nonce"),
}
return
}
nonceStr := base64.StdEncoding.EncodeToString(nonce[:])
data, err := json.Marshal(input)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to marshal json data"),
}
return
}
encByt := secretbox.Seal(nil, data, nonce, a.key)
sigEncByt := sign.Sign(nil, encByt, a.privateKey)
sigEncStr := base64.StdEncoding.EncodeToString(sigEncByt)
hashFunc := hmac.New(sha512.New, a.secret[:])
hashFunc.Write([]byte(sigEncStr))
rawSignature := hashFunc.Sum(nil)
sigStr := base64.StdEncoding.EncodeToString(rawSignature)
msg = &Message{
Nonce: nonceStr,
Message: sigEncStr,
Signature: sigStr,
}
return
}
func (a *AsymNaclHmac) SealJson(input any) (output string, err error) {
msg, err := a.Seal(input)
if err != nil {
return
}
outputByt, err := json.Marshal(msg)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to marshal message"),
}
return
}
output = string(outputByt)
return
}
func (a *AsymNaclHmac) Unseal(msg *Message, output any) (err error) {
if a.key == nil || a.secret == nil || a.publicKey == nil {
err = &errortypes.AuthenticationError{
errors.New("crypto: Private key and secret not loaded"),
}
return
}
hashFunc := hmac.New(sha512.New, a.secret[:])
hashFunc.Write([]byte(msg.Message))
rawSignature := hashFunc.Sum(nil)
sigStr := base64.StdEncoding.EncodeToString(rawSignature)
if subtle.ConstantTimeCompare([]byte(sigStr), []byte(msg.Signature)) != 1 {
err = &errortypes.AuthenticationError{
errors.New("crypto: Invalid message signature"),
}
return
}
nonceByt, err := base64.StdEncoding.DecodeString(msg.Nonce)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to decode nonce"),
}
return
}
if len(nonceByt) != 24 {
err = &errortypes.ParseError{
errors.New("crypto: Invalid nonce length"),
}
return
}
if a.nonceHandler != nil {
err = a.nonceHandler(nonceByt)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Nonce validate failed"),
}
return
}
}
nonce := new([24]byte)
copy(nonce[:], nonceByt)
sigEncByt, err := base64.StdEncoding.DecodeString(msg.Message)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to decode message"),
}
return
}
encByt, valid := sign.Open(nil, sigEncByt, a.publicKey)
if !valid {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to verify message signature"),
}
return
}
decByt, ok := secretbox.Open(nil, encByt, nonce, a.key)
if !ok {
err = &errortypes.AuthenticationError{
errors.New("crypto: Failed to decrypt message"),
}
return
}
err = json.Unmarshal(decByt, output)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to unmarshal data"),
}
return
}
return
}
func (a *AsymNaclHmac) UnsealJson(input string, output any) (err error) {
msg := &Message{}
err = json.Unmarshal([]byte(input), msg)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to unmarshal message"),
}
return
}
err = a.Unseal(msg, output)
if err != nil {
return
}
return
}
func (a *AsymNaclHmac) Export() AsymNaclHmacKey {
return AsymNaclHmacKey{
Key: base64.StdEncoding.EncodeToString(a.key[:]),
Secret: base64.StdEncoding.EncodeToString(a.secret[:]),
PublicKey: base64.StdEncoding.EncodeToString(a.publicKey[:]),
PrivateKey: base64.StdEncoding.EncodeToString(a.privateKey[:]),
}
}
func (a *AsymNaclHmac) Import(key AsymNaclHmacKey) (err error) {
keyByt, err := base64.StdEncoding.DecodeString(key.Key)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to decode key"),
}
return
}
if len(keyByt) != 32 {
err = &errortypes.ParseError{
errors.New("crypto: Invalid key length"),
}
return
}
secrByt, err := base64.StdEncoding.DecodeString(key.Secret)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to decode secret key"),
}
return
}
if len(secrByt) != 32 {
err = &errortypes.ParseError{
errors.New("crypto: Invalid secret key length"),
}
return
}
pubKeyByt, err := base64.StdEncoding.DecodeString(
key.PublicKey)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to decode public key"),
}
return
}
if len(pubKeyByt) != 32 {
err = &errortypes.ParseError{
errors.New("crypto: Invalid public key length"),
}
return
}
if key.PrivateKey != "" {
privKeyByt, e := base64.StdEncoding.DecodeString(
key.PrivateKey)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "crypto: Failed to decode private key"),
}
return
}
if len(privKeyByt) != 64 {
err = &errortypes.ParseError{
errors.New("crypto: Invalid private key length"),
}
return
}
if a.privateKey == nil {
a.privateKey = new([64]byte)
}
copy(a.privateKey[:], privKeyByt)
}
if a.key == nil {
a.key = new([32]byte)
}
if a.secret == nil {
a.secret = new([32]byte)
}
if a.publicKey == nil {
a.publicKey = new([32]byte)
}
copy(a.key[:], keyByt)
copy(a.secret[:], secrByt)
copy(a.publicKey[:], pubKeyByt)
return
}
func (a *AsymNaclHmac) Generate() (err error) {
key := new([32]byte)
_, err = io.ReadFull(rand.Reader, key[:])
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to generate key"),
}
return
}
secKey := new([32]byte)
_, err = io.ReadFull(rand.Reader, secKey[:])
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "crypto: Failed to generate secret key"),
}
return
}
signPubKey, signPrivKey, err := sign.GenerateKey(rand.Reader)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "crypto: Failed to generate signing key"),
}
return
}
a.key = key
a.secret = secKey
a.publicKey = signPubKey
a.privateKey = signPrivKey
return
}
================================================
FILE: redirect/go.mod
================================================
module github.com/pritunl/pritunl-cloud/redirect
go 1.24.0
toolchain go1.24.6
require (
github.com/pritunl/tools v1.2.6
golang.org/x/crypto v0.45.0
)
require golang.org/x/sys v0.38.0 // indirect
================================================
FILE: redirect/go.sum
================================================
github.com/pritunl/tools v1.2.6 h1:rxqJjmLEHc/SLf8wjWpZX0J+2JoRO0ShbMZ/R19efY8=
github.com/pritunl/tools v1.2.6/go.mod h1:BiNzTb2ZCesQ5k/Mx0mhOwGXNJNdZk+4jqg39GjRXKU=
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
================================================
FILE: redirect/main.go
================================================
package main
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/redirect/crypto"
"github.com/pritunl/tools/errors"
"github.com/pritunl/tools/errortypes"
"github.com/pritunl/tools/logger"
)
func main() {
publicKey := os.Getenv("PUBLIC_KEY")
key := os.Getenv("KEY")
secret := os.Getenv("SECRET")
os.Unsetenv("PUBLIC_KEY")
os.Unsetenv("KEY")
os.Unsetenv("SECRET")
logger.Init()
logger.AddHandler(func(record *logger.Record) {
fmt.Print(record.String())
})
err := runServer(publicKey, key, secret)
if err != nil {
logger.WithFields(logger.Fields{
"error": err,
}).Error("redirect: Redirect server error")
os.Exit(1)
}
}
func runServer(publicKey, key, secret string) (err error) {
webPort, err := strconv.Atoi(os.Getenv("WEB_PORT"))
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(err, "redirect: Failed to parse web port"),
}
return
}
naclKey := crypto.AsymNaclHmacKey{
PublicKey: publicKey,
Key: key,
Secret: secret,
}
box := &crypto.AsymNaclHmac{}
err = box.Import(naclKey)
if err != nil {
return
}
logger.WithFields(logger.Fields{
"port": 80,
"web_port": webPort,
}).Info("redirect: Starting HTTP redirect server")
go sandboxTest()
file := os.NewFile(uintptr(3), "systemd-socket")
listener, err := net.FileListener(file)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "redirect: Failed to get socket listener"),
}
return
}
server := &http.Server{
Addr: ":80",
ReadTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
Handler: http.HandlerFunc(func(
w http.ResponseWriter, req *http.Request) {
if req.Method == "GET" && strings.HasPrefix(req.URL.Path,
"/.well-known/acme-challenge/") {
pathSplit := strings.Split(req.URL.Path, "/")
token := pathSplit[len(pathSplit)-1]
chal := GetChallenge(token)
if chal == nil {
w.WriteHeader(404)
fmt.Fprint(w, "404 page not found")
return
}
w.WriteHeader(200)
fmt.Fprint(w, chal.Response)
return
} else if req.Method == "POST" && req.URL.Path == "/token" {
bodyBytes := make([]byte, 8096)
n, err := io.LimitReader(req.Body, 8096).Read(bodyBytes)
if err != nil && err != io.EOF {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Internal server error")
return
}
bodyBytes = bodyBytes[:n]
chal := &Challenge{}
err = box.UnsealJson(string(bodyBytes), chal)
if err != nil && err != io.EOF {
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprint(w, "Failed to authorize")
return
}
AddChallenge(chal)
w.WriteHeader(200)
fmt.Fprint(w, "success")
return
}
req.URL.Scheme = "https"
req.URL.Host = StripPort(req.Host)
if webPort != 443 {
req.URL.Host += fmt.Sprintf(":%d", webPort)
}
http.Redirect(w, req, req.URL.String(),
http.StatusMovedPermanently)
}),
}
err = server.Serve(listener)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "redirect: Failed to bind web server"),
}
return
}
return
}
func sandboxTest() {
time.Sleep(3 * time.Second)
client := &http.Client{
Timeout: 3 * time.Second,
Transport: &http.Transport{
TLSHandshakeTimeout: 3 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
req, err := http.NewRequest(
"GET",
"https://127.0.0.1",
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "acme: Sandbox test request failed"),
}
return
}
resp, err := client.Do(req)
if err == nil {
logger.WithFields(logger.Fields{
"status_code": resp.StatusCode,
}).Error("redirect: Sandbox escape test failed")
} else {
logger.Info("redirect: Sandbox escape test successful")
}
}
================================================
FILE: redirect/utils.go
================================================
package main
import (
"strings"
)
func StripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return hostport
}
n := strings.Count(hostport, ":")
if n > 1 {
if i := strings.IndexByte(hostport, ']'); i != -1 {
return strings.TrimPrefix(hostport[:i], "[")
}
return hostport
}
return hostport[:colon]
}
================================================
FILE: relations/definitions/block.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
var Block = relations.Query{
Label: "Block",
Collection: "blocks",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "subnets",
Label: "Subnets",
}},
Relations: []relations.Relation{{
Key: "blocks_ip",
Label: "Block IP",
From: "blocks_ip",
LocalField: "_id",
ForeignField: "block",
BlockDelete: true,
Sort: map[string]int{
"ip": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "ip",
Label: "IP",
Format: func(vals ...any) any {
return utils.Int2IpAddress(vals[0].(int64)).String()
},
}, {
Key: "instance",
}},
Relations: []relations.Relation{{
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "instance",
ForeignField: "_id",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}},
}},
}
func init() {
relations.Register("block", Block)
}
================================================
FILE: relations/definitions/certificate.go
================================================
package definitions
import (
"github.com/pritunl/pritunl-cloud/relations"
)
var Certificate = relations.Query{
Label: "Certificate",
Collection: "certificates",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "nodes",
Label: "Node",
From: "nodes",
LocalField: "_id",
ForeignField: "certificates",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "types",
Label: "Modes",
}, {
Key: "admin_domain",
Label: "Admin Domain",
}, {
Key: "user_domain",
Label: "User Domain",
}, {
Key: "webauthn_domain",
Label: "WebAuthn Domain",
}, {
Key: "network_mode",
Label: "Network Mode IPv4",
}, {
Key: "network_mode6",
Label: "Network Mode IPv6",
}},
}, {
Key: "balancers",
Label: "Load Balancer",
From: "balancers",
LocalField: "_id",
ForeignField: "certificates",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "state",
Label: "State",
}},
}},
}
func init() {
relations.Register("certificate", Certificate)
}
================================================
FILE: relations/definitions/datacenter.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Datacenter = relations.Query{
Label: "Datacenter",
Collection: "datacenters",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
Relations: []relations.Relation{{
Key: "zones",
Label: "Zone",
From: "zones",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "nodes",
Label: "Node",
From: "nodes",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "types",
Label: "Modes",
}, {
Key: "admin_domain",
Label: "Admin Domain",
}, {
Key: "user_domain",
Label: "User Domain",
}, {
Key: "webauthn_domain",
Label: "WebAuthn Domain",
}, {
Key: "network_mode",
Label: "Network Mode IPv4",
}, {
Key: "network_mode6",
Label: "Network Mode IPv6",
}},
}, {
Key: "vpcs",
Label: "VPC",
From: "vpcs",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "vpc_id",
Label: "VPC ID",
}, {
Key: "network",
Label: "Network IPv4",
}},
}, {
Key: "balancers",
Label: "Load Balancer",
From: "balancers",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "state",
Label: "State",
}},
}, {
Key: "deployments",
Label: "Deployment",
From: "deployments",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "kind",
Label: "Kind",
}, {
Key: "state",
Label: "State",
}, {
Key: "status",
Label: "Status",
}, {
Key: "timestamp",
Label: "Age",
Format: func(vals ...any) any {
val := vals[0]
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}},
}, {
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "datacenter",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"index": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "index",
Label: "Index",
}, {
Key: "size",
Label: "Size",
}},
}, {
Key: "nodeports",
Label: "Nodeport",
From: "nodeports",
LocalField: "_id",
ForeignField: "datacenter",
BlockDelete: true,
Sort: map[string]int{
"port": 1,
},
Project: []relations.Project{{
Key: "port",
Label: "Port",
}, {
Key: "protocol",
Label: "Protocol",
}},
}},
}
func init() {
relations.Register("datacenter", Datacenter)
}
================================================
FILE: relations/definitions/definitions.go
================================================
package definitions
func Init() {
}
================================================
FILE: relations/definitions/firewall.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Firewall = relations.Query{
Label: "Firewall",
Collection: "firewalls",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "roles",
ForeignField: "roles",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}},
}
func init() {
relations.Register("firewall", Firewall)
}
================================================
FILE: relations/definitions/instance.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Instance = relations.Query{
Label: "Instance",
Collection: "instances",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}, {
Key: "node",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
Relations: []relations.Relation{{
Key: "nodes",
Label: "Node",
From: "nodes",
LocalField: "node",
ForeignField: "_id",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "types",
Label: "Modes",
}, {
Key: "network_mode",
Label: "Network Mode IPv4",
}, {
Key: "network_mode6",
Label: "Network Mode IPv6",
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "instance",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "type",
Label: "Type",
}, {
Key: "state",
Label: "State",
}, {
Key: "size",
Label: "Size",
}},
},
// {
// TODO Match organization
// Key: "firewalls",
// Label: "Firewall",
// From: "firewalls",
// LocalField: "roles",
// ForeignField: "roles",
// Sort: map[string]int{
// "name": 1,
// },
// Project: []relations.Project{{
// Key: "name",
// Label: "Name",
// }, {
// Key: "roles",
// Label: "Roles",
// }, {
// Key: "ingress",
// Label: "Ingress",
// Format: func(vals ...any) any {
// rules := vals[0].(bson.A)
// rulesStr := []string{}
// for _, ruleInf := range rules {
// rule := ruleInf.(primitive.M)
// ruleStr := ""
// protocol := rule["protocol"].(string)
// port := rule["port"].(string)
// sourceIps := rule["source_ips"].(bson.A)
// switch protocol {
// case firewall.All, firewall.Icmp:
// ruleStr = protocol
// default:
// ruleStr = port + "/" + protocol
// }
// ruleStr += " ("
// sourceIpsLen := len(sourceIps)
// for i, sourceIp := range sourceIps {
// ruleStr += sourceIp.(string)
// if i+1 < sourceIpsLen {
// ruleStr += ", "
// }
// }
// ruleStr += ")"
// rulesStr = append(rulesStr, ruleStr)
// }
// return rulesStr
// },
// }},
// }
},
}
func init() {
relations.Register("instance", Instance)
}
================================================
FILE: relations/definitions/node.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Node = relations.Query{
Label: "Node",
Collection: "nodes",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
Relations: []relations.Relation{{
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "node",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "node",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "type",
Label: "Type",
}, {
Key: "state",
Label: "State",
}, {
Key: "size",
Label: "Size",
}},
},
// {
// TODO Match organization
// Key: "firewalls",
// Label: "Firewall",
// From: "firewalls",
// LocalField: "roles",
// ForeignField: "roles",
// Sort: map[string]int{
// "name": 1,
// },
// Project: []relations.Project{{
// Key: "name",
// Label: "Name",
// }, {
// Key: "roles",
// Label: "Roles",
// }, {
// Key: "ingress",
// Label: "Ingress",
// Format: func(vals ...any) any {
// rules := vals[0].(bson.A)
// rulesStr := []string{}
// for _, ruleInf := range rules {
// rule := ruleInf.(primitive.M)
// ruleStr := ""
// protocol := rule["protocol"].(string)
// port := rule["port"].(string)
// sourceIps := rule["source_ips"].(bson.A)
// switch protocol {
// case firewall.All, firewall.Icmp:
// ruleStr = protocol
// default:
// ruleStr = port + "/" + protocol
// }
// ruleStr += " ("
// sourceIpsLen := len(sourceIps)
// for i, sourceIp := range sourceIps {
// ruleStr += sourceIp.(string)
// if i+1 < sourceIpsLen {
// ruleStr += ", "
// }
// }
// ruleStr += ")"
// rulesStr = append(rulesStr, ruleStr)
// }
// return rulesStr
// },
// }},
// }
},
}
func init() {
relations.Register("node", Node)
}
================================================
FILE: relations/definitions/organization.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Organization = relations.Query{
Label: "Organization",
Collection: "organizations",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "certificates",
Label: "Certificate",
From: "certificates",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "secrets",
Label: "Secret",
From: "secrets",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "vpc",
Label: "VPCs",
From: "vpc",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "domains",
Label: "Domain",
From: "domains",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "balancers",
Label: "Load Balancer",
From: "balancers",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "images",
Label: "Image",
From: "images",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "plans",
Label: "Plan",
From: "plans",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "size",
Label: "Size",
}},
}, {
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}, {
Key: "pods",
Label: "Pod",
From: "pods",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "deployments",
Label: "Deployment",
From: "deployments",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "firewalls",
Label: "Firewall",
From: "firewalls",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "authorities",
Label: "Authority",
From: "authorities",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}, {
Key: "alerts",
Label: "Alert",
From: "alerts",
LocalField: "_id",
ForeignField: "organization",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
}},
}
func init() {
relations.Register("organization", Organization)
}
================================================
FILE: relations/definitions/pod.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Pod = relations.Query{
Label: "Pod",
Collection: "pods",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
Relations: []relations.Relation{{
Key: "units",
Label: "Unit",
From: "units",
LocalField: "_id",
ForeignField: "pod",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "kind",
Label: "Kind",
}, {
Key: "count",
Label: "Count",
}},
Relations: []relations.Relation{{
Key: "deployments",
Label: "Deployment",
From: "deployments",
LocalField: "_id",
ForeignField: "unit",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "kind",
Label: "Kind",
}, {
Key: "state",
Label: "State",
}, {
Key: "status",
Label: "Status",
}, {
Key: "timestamp",
Label: "Age",
Format: func(vals ...any) any {
val := vals[0]
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}},
Relations: []relations.Relation{{
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "deployment",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "deployment",
Sort: map[string]int{
"index": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "index",
Label: "Index",
}, {
Key: "size",
Label: "Size",
}},
}},
}},
}},
}
func init() {
relations.Register("pod", Pod)
}
================================================
FILE: relations/definitions/policy.go
================================================
package definitions
import (
"github.com/pritunl/pritunl-cloud/relations"
)
var Policy = relations.Query{
Label: "Policy",
Collection: "policies",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "users",
Label: "User",
From: "users",
LocalField: "roles",
ForeignField: "roles",
Sort: map[string]int{
"username": 1,
},
Project: []relations.Project{{
Key: "username",
Label: "Username",
}, {
Key: "type",
Label: "Type",
}},
}},
}
func init() {
relations.Register("policy", Policy)
}
================================================
FILE: relations/definitions/secret.go
================================================
package definitions
import (
"github.com/pritunl/pritunl-cloud/relations"
)
var Secret = relations.Query{
Label: "Secret",
Collection: "secrets",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}},
Relations: []relations.Relation{{
Key: "domains",
Label: "Domain",
From: "domains",
LocalField: "_id",
ForeignField: "secret",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "root_domain",
Label: "Root Domain",
}},
}, {
Key: "certificates",
Label: "Certificate",
From: "certificates",
LocalField: "_id",
ForeignField: "acme_secret",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "type",
Label: "Type",
}, {
Key: "acme_domains",
Label: "Lets Encrypt Domains",
}},
}},
}
func init() {
relations.Register("secret", Secret)
}
================================================
FILE: relations/definitions/shape.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Shape = relations.Query{
Label: "Shape",
Collection: "shapes",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "nodes",
Label: "Node",
From: "nodes",
LocalField: "roles",
ForeignField: "roles",
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "types",
Label: "Modes",
}, {
Key: "admin_domain",
Label: "Admin Domain",
}, {
Key: "user_domain",
Label: "User Domain",
}, {
Key: "webauthn_domain",
Label: "WebAuthn Domain",
}, {
Key: "network_mode",
Label: "Network Mode IPv4",
}, {
Key: "network_mode6",
Label: "Network Mode IPv6",
}},
}, {
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "shape",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}},
}
func init() {
relations.Register("shape", Shape)
}
================================================
FILE: relations/definitions/vpc.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Vpc = relations.Query{
Label: "VPC",
Collection: "vpcs",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "vpc",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}},
}
func init() {
relations.Register("vpc", Vpc)
}
================================================
FILE: relations/definitions/zone.go
================================================
package definitions
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/vm"
)
var Zone = relations.Query{
Label: "Zone",
Collection: "zones",
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "roles",
Label: "Roles",
}},
Relations: []relations.Relation{{
Key: "nodes",
Label: "Node",
From: "nodes",
LocalField: "_id",
ForeignField: "zone",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "types",
Label: "Modes",
}, {
Key: "admin_domain",
Label: "Admin Domain",
}, {
Key: "user_domain",
Label: "User Domain",
}, {
Key: "webauthn_domain",
Label: "WebAuthn Domain",
}, {
Key: "network_mode",
Label: "Network Mode IPv4",
}, {
Key: "network_mode6",
Label: "Network Mode IPv6",
}},
}, {
Key: "deployments",
Label: "Deployment",
From: "deployments",
LocalField: "_id",
ForeignField: "zone",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "kind",
Label: "Kind",
}, {
Key: "state",
Label: "State",
}, {
Key: "status",
Label: "Status",
}, {
Key: "timestamp",
Label: "Age",
Format: func(vals ...any) any {
val := vals[0]
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}},
}, {
Key: "instances",
Label: "Instance",
From: "instances",
LocalField: "_id",
ForeignField: "zone",
BlockDelete: true,
Sort: map[string]int{
"name": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Keys: []string{
"action",
"state",
},
Label: "Status",
Format: func(vals ...any) any {
action, _ := vals[0].(string)
state, _ := vals[1].(string)
switch action {
case instance.Start:
switch state {
case vm.Starting:
return "Starting"
case vm.Running:
return "Running"
case vm.Stopped:
return "Starting"
case vm.Failed:
return "Starting"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Provisioning"
case "":
return "Provisioning"
}
case instance.Cleanup:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopping"
case vm.Failed:
return "Stopping"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopping"
case "":
return "Stopping"
}
case instance.Stop:
switch state {
case vm.Starting:
return "Stopping"
case vm.Running:
return "Stopping"
case vm.Stopped:
return "Stopped"
case vm.Failed:
return "Failed"
case vm.Updating:
return "Updating"
case vm.Provisioning:
return "Stopped"
case "":
return "Stopped"
}
case instance.Restart:
return "Restarting"
case instance.Destroy:
return "Destroying"
}
return state
},
}, {
Keys: []string{
"timestamp",
"action",
"state",
},
Label: "Uptime",
Format: func(vals ...any) any {
val := vals[0]
action, _ := vals[1].(string)
state, _ := vals[2].(string)
isActive := action == instance.Start ||
state == vm.Running || state == vm.Starting ||
state == vm.Provisioning
if !isActive {
return "-"
}
if mongoTime, ok := val.(bson.DateTime); ok {
valTime := mongoTime.Time()
return systemd.FormatUptimeShort(valTime)
}
if goTime, ok := val.(time.Time); ok {
return systemd.FormatUptimeShort(goTime)
}
return "-"
},
}, {
Key: "processors",
Label: "Processors",
}, {
Key: "memory",
Label: "Memory",
}, {
Key: "private_ips",
Label: "Private IPv4",
}, {
Key: "public_ips",
Label: "Public IPv4",
}},
}, {
Key: "disks",
Label: "Disk",
From: "disks",
LocalField: "_id",
ForeignField: "zone",
BlockDelete: true,
Sort: map[string]int{
"index": 1,
},
Project: []relations.Project{{
Key: "name",
Label: "Name",
}, {
Key: "index",
Label: "Index",
}, {
Key: "size",
Label: "Size",
}},
}},
}
func init() {
relations.Register("zone", Zone)
}
================================================
FILE: relations/registry.go
================================================
package relations
import (
"fmt"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/imds/server/errortypes"
)
var registry = map[string]Query{}
func Register(kind string, definition Query) {
registry[kind] = definition
}
func Aggregate(db *database.Database, kind string, id bson.ObjectID) (
resp *Response, err error) {
definition, ok := registry[kind]
if !ok {
return
}
definition.Id = id
resp, err = definition.Aggregate(db)
if err != nil {
return
}
return
}
func AggregateOrg(db *database.Database, kind string,
orgId, id bson.ObjectID) (resp *Response, err error) {
definition, ok := registry[kind]
if !ok {
return
}
definition.Id = id
definition.Organization = orgId
resp, err = definition.Aggregate(db)
if err != nil {
return
}
return
}
func blockDelete(resources []Resource) string {
for _, resource := range resources {
if resource.BlockDelete {
return resource.Type
}
for _, related := range resource.Relations {
label := blockDelete(related.Resources)
if label != "" {
return label
}
}
}
return ""
}
func CanDelete(db *database.Database, kind string, id bson.ObjectID) (
errData *errortypes.ErrorData, err error) {
definition, ok := registry[kind]
if !ok {
return
}
definition.Id = id
resp, err := definition.Aggregate(db)
if err != nil {
return
}
if resp.DeleteProtection {
errData = &errortypes.ErrorData{
Error: "delete_protected_resource",
Message: "Cannot delete resource with delete protection enabled",
}
return
}
labels := []string{}
for _, related := range resp.Relations {
label := blockDelete(related.Resources)
if label != "" {
labels = append(labels, label)
}
}
if len(labels) > 0 {
errData = &errortypes.ErrorData{
Error: "related_resources_exist",
Message: fmt.Sprintf(
"Related [%s] resources must be deleted first. "+
"Check resource overview",
strings.Join(labels, ", "),
),
}
return
}
return
}
func CanDeleteOrg(db *database.Database, kind string,
orgId, id bson.ObjectID) (errData *errortypes.ErrorData, err error) {
definition, ok := registry[kind]
if !ok {
return
}
definition.Id = id
definition.Organization = orgId
resp, err := definition.Aggregate(db)
if err != nil {
return
}
if resp.DeleteProtection {
errData = &errortypes.ErrorData{
Error: "delete_protected_resource",
Message: "Cannot delete resource with delete protection enabled",
}
return
}
labels := []string{}
for _, related := range resp.Relations {
label := blockDelete(related.Resources)
if label != "" {
labels = append(labels, label)
}
}
if len(labels) > 0 {
errData = &errortypes.ErrorData{
Error: "related_resources_exist",
Message: fmt.Sprintf(
"Related [%s] resources must be deleted first. "+
"Check resource overview",
strings.Join(labels, ", "),
),
}
return
}
return
}
func CanDeleteAll(db *database.Database, kind string,
ids []bson.ObjectID) (errData *errortypes.ErrorData, err error) {
for _, id := range ids {
errData, err = CanDelete(db, kind, id)
if err != nil {
return
}
if errData != nil {
return
}
}
return
}
func CanDeleteOrgAll(db *database.Database, kind string,
orgId bson.ObjectID, ids []bson.ObjectID) (
errData *errortypes.ErrorData, err error) {
for _, id := range ids {
errData, err = CanDeleteOrg(db, kind, orgId, id)
if err != nil {
return
}
if errData != nil {
return
}
}
return
}
================================================
FILE: relations/relations.go
================================================
package relations
import (
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
type Query struct {
Id any
Organization bson.ObjectID
Label string
Collection string
Project []Project
Relations []Relation
}
type Relation struct {
Key string
Label string
From string
LocalField string
ForeignField string
Sort map[string]int
Project []Project
Relations []Relation
BlockDelete bool
}
type Project struct {
Key string
Keys []string
Label string
Format func(values ...any) any
}
func (r *Query) addRelation(pipeline []bson.M, relation Relation) []bson.M {
lookup := bson.M{
"from": relation.From,
"localField": relation.LocalField,
"foreignField": relation.ForeignField,
"as": relation.From,
}
if len(relation.Project) > 0 || len(relation.Sort) > 0 ||
len(relation.Relations) > 0 {
nestedPipeline := []bson.M{}
if len(relation.Project) > 0 {
projection := bson.M{
"_id": 1,
}
for _, proj := range relation.Project {
if len(proj.Keys) > 0 {
for _, key := range proj.Keys {
projection[key] = 1
}
} else {
projection[proj.Key] = 1
}
}
nestedPipeline = append(nestedPipeline, bson.M{
"$project": projection,
})
}
if len(relation.Sort) > 0 {
nestedPipeline = append(nestedPipeline, bson.M{
"$sort": relation.Sort,
})
}
for _, nestedRelation := range relation.Relations {
nestedPipeline = r.addRelation(nestedPipeline, nestedRelation)
}
lookup["pipeline"] = nestedPipeline
}
return append(pipeline, bson.M{
"$lookup": lookup,
})
}
func (r *Query) convertToResponse(doc bson.M) *Response {
response := &Response{
Id: doc["_id"],
Label: r.Label,
Fields: []Field{},
Relations: []Related{},
}
deleteProtection, _ := doc["delete_protection"].(bool)
if deleteProtection {
response.DeleteProtection = true
}
for _, proj := range r.Project {
if proj.Label == "" {
continue
}
if len(proj.Keys) > 0 {
value, ok := doc[proj.Keys[0]]
if ok {
if proj.Format != nil {
values := []any{}
for _, key := range proj.Keys {
val, ok := doc[key]
if !ok {
values = append(values, nil)
} else {
values = append(values, val)
}
}
value = proj.Format(values...)
}
response.Fields = append(response.Fields, Field{
Key: proj.Key,
Label: proj.Label,
Value: value,
})
}
} else {
value, ok := doc[proj.Key]
if ok {
if proj.Format != nil {
value = proj.Format(value)
}
response.Fields = append(response.Fields, Field{
Key: proj.Key,
Label: proj.Label,
Value: value,
})
}
}
}
for _, relation := range r.Relations {
docs, ok := doc[relation.From].(bson.A)
if ok {
response.Relations = append(
response.Relations,
r.convertToRelated(relation, docs),
)
}
}
return response
}
func (r *Query) convertToRelated(relation Relation,
docs bson.A) Related {
related := Related{
Label: relation.Label,
Resources: []Resource{},
}
for _, docInf := range docs {
var doc bson.M
if bsonDoc, ok := docInf.(bson.D); ok {
doc = make(bson.M)
for _, elem := range bsonDoc {
doc[elem.Key] = elem.Value
}
} else if mapDoc, ok := docInf.(bson.M); ok {
doc = mapDoc
} else {
continue
}
resource := Resource{
Id: doc["_id"],
Type: relation.Label,
Fields: []Field{},
Relations: []Related{},
BlockDelete: relation.BlockDelete,
}
for _, proj := range relation.Project {
if proj.Label == "" {
continue
}
if len(proj.Keys) > 0 {
value, ok := doc[proj.Keys[0]]
if ok {
if proj.Format != nil {
values := []any{}
for _, key := range proj.Keys {
val, ok := doc[key]
if !ok {
values = append(values, nil)
} else {
values = append(values, val)
}
}
value = proj.Format(values...)
}
resource.Fields = append(resource.Fields, Field{
Key: proj.Key,
Label: proj.Label,
Value: value,
})
}
} else {
value, ok := doc[proj.Key]
if ok {
if proj.Format != nil {
value = proj.Format(value)
}
resource.Fields = append(resource.Fields, Field{
Key: proj.Key,
Label: proj.Label,
Value: value,
})
}
}
}
for _, relation := range relation.Relations {
docs, ok := doc[relation.From].(bson.A)
if ok {
resource.Relations = append(
resource.Relations,
r.convertToRelated(relation, docs),
)
}
}
related.Resources = append(related.Resources, resource)
}
return related
}
func (r *Query) Aggregate(db *database.Database) (
resp *Response, err error) {
coll := db.GetCollection(r.Collection)
query := bson.M{
"_id": r.Id,
}
if !r.Organization.IsZero() {
query["organization"] = r.Organization
}
pipeline := []bson.M{
{
"$match": query,
},
}
if len(r.Project) > 0 {
projection := bson.M{"_id": 1, "delete_protection": 1}
for _, proj := range r.Project {
if len(proj.Keys) > 0 {
for _, key := range proj.Keys {
projection[key] = 1
}
} else {
projection[proj.Key] = 1
}
}
pipeline = append(pipeline, bson.M{"$project": projection})
}
for _, relation := range r.Relations {
pipeline = r.addRelation(pipeline, relation)
}
cursor, err := coll.Aggregate(db, pipeline)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
var results []bson.M
err = cursor.All(db, &results)
if err != nil {
err = database.ParseError(err)
return
}
if len(results) == 0 {
err = &database.NotFoundError{
errors.New("relations: Resource not found"),
}
return
}
resp = r.convertToResponse(results[0])
return
}
================================================
FILE: relations/response.go
================================================
package relations
import (
"fmt"
"reflect"
"strings"
)
type Response struct {
Id any
Label string
Fields []Field
Relations []Related
DeleteProtection bool
}
type Related struct {
Label string
Resources []Resource
}
type Resource struct {
Id any
Type string
Fields []Field
Relations []Related
BlockDelete bool
}
type Field struct {
Key string
Label string
Value any
}
func (r *Response) Yaml() string {
var output strings.Builder
output.WriteString(fmt.Sprintf("ID: %v\n", r.Id))
output.WriteString(fmt.Sprintf("Label: %s\n", r.Label))
if len(r.Fields) > 0 {
output.WriteString("Fields:\n")
for _, field := range r.Fields {
output.WriteString(fmt.Sprintf(
" %s: %s\n",
field.Label,
field.yaml(),
))
}
}
if len(r.Relations) > 0 {
output.WriteString("Relations:\n")
for _, rel := range r.Relations {
for _, resource := range rel.Resources {
output.WriteString(resource.yaml(0))
}
}
}
return strings.TrimRight(output.String(), "\n")
}
func (r Resource) yaml(indent int) string {
var output strings.Builder
indentStr := strings.Repeat(" ", indent)
output.WriteString(fmt.Sprintf("%s- ID: %v\n", indentStr, r.Id))
output.WriteString(fmt.Sprintf("%s Type: %s\n", indentStr, r.Type))
if len(r.Fields) > 0 {
output.WriteString(fmt.Sprintf("%s Fields:\n", indentStr))
for _, field := range r.Fields {
output.WriteString(fmt.Sprintf(
"%s %s: %s\n",
indentStr,
field.Label,
field.yaml(),
))
}
}
if len(r.Relations) > 0 {
output.WriteString(fmt.Sprintf("%s Relations:\n", indentStr))
for _, rel := range r.Relations {
for _, resource := range rel.Resources {
output.WriteString(resource.yaml(indent + 2))
}
}
}
return output.String()
}
func (f Field) yaml() string {
if f.Value == nil {
return "null"
}
v := reflect.ValueOf(f.Value)
if v.Kind() == reflect.Slice || v.Kind() == reflect.Array {
var items []string
for i := 0; i < v.Len(); i++ {
item := v.Index(i).Interface()
items = append(items, fmt.Sprintf("%v", item))
}
return "[" + strings.Join(items, ", ") + "]"
}
if v.Kind() == reflect.String {
s := f.Value.(string)
if strings.ContainsAny(s, ":#{}[]&*!|>'\"\n") {
return "\"" + strings.ReplaceAll(s, "\"", "\\\"") + "\""
}
return s
}
return fmt.Sprintf("%v", f.Value)
}
================================================
FILE: relations/utils.go
================================================
package relations
import (
"encoding/json"
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func PrintPipeline(pipeline []bson.M) {
println("**************************************************")
for _, stage := range pipeline {
jsonData, err := json.MarshalIndent(stage, "", " ")
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "relations: Failed to marshal json"),
}
fmt.Println(err.Error())
continue
}
fmt.Printf("%s\n", string(jsonData))
}
println("**************************************************")
}
func PrintResults(results []bson.M) {
jsonData, err := json.MarshalIndent(results, "", " ")
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "relations: Failed to marshal json"),
}
fmt.Println(err.Error())
return
}
println("**************************************************")
fmt.Printf("%s\n", string(jsonData))
println("**************************************************")
}
================================================
FILE: render/constants.go
================================================
package render
const (
RendersDir = "/dev/dri/by-path"
)
================================================
FILE: render/render.go
================================================
package render
import (
"io/ioutil"
"path"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
renders = []string{}
lastRendersSync time.Time
)
func GetRenders() (rendrs []string, err error) {
if time.Since(lastRendersSync) < 300*time.Second {
rendrs = renders
return
}
rendersNew := []string{}
exists, err := utils.ExistsDir(RendersDir)
if err != nil {
return
}
if !exists {
return
}
renderFiles, err := ioutil.ReadDir(RendersDir)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "backup: Failed to read renders directory"),
}
return
}
for _, item := range renderFiles {
name := item.Name()
if !strings.Contains(name, "render") {
continue
}
rendersNew = append(rendersNew, item.Name())
}
renders = rendersNew
lastRendersSync = time.Now()
rendrs = rendersNew
return
}
func GetRender(render string) (pth string, err error) {
rendrs, err := GetRenders()
if err != nil {
return
}
for _, rendr := range rendrs {
if rendr == render {
pth = path.Join(RendersDir, rendr)
return
}
}
err = &errortypes.ReadError{
errors.Newf("render: Failed to find render '%s'", render),
}
return
}
================================================
FILE: requires/errors.go
================================================
package requires
import (
"github.com/dropbox/godropbox/errors"
)
type InitError struct {
errors.DropboxError
}
================================================
FILE: requires/requires.go
================================================
// Init system with before and after constraints.
package requires
import (
"container/list"
"fmt"
"os"
"strings"
"github.com/dropbox/godropbox/container/set"
)
var (
modules = list.New()
)
type Module struct {
name string
before set.Set
after set.Set
Handler func() (err error)
}
func (m *Module) Before(name string) {
m.before.Add(name)
}
func (m *Module) After(name string) {
m.after.Add(name)
}
func New(name string) (module *Module) {
module = &Module{
name: name,
before: set.NewSet(),
after: set.NewSet(),
}
modules.PushBack(module)
return
}
func Init(ignore []string) {
loaded := false
ignoreSet := set.NewSet()
if ignore != nil {
for _, name := range ignore {
ignoreSet.Add(name)
}
}
Loop:
for count := 0; count < 100; count += 1 {
i := modules.Front()
for i != nil {
module := i.Value.(*Module)
j := i.Prev()
for j != nil {
if module.before.Contains(j.Value.(*Module).name) {
modules.MoveBefore(i, j)
continue Loop
}
j = j.Prev()
}
j = i.Next()
for j != nil {
if module.after.Contains(j.Value.(*Module).name) {
modules.MoveAfter(i, j)
continue Loop
}
j = j.Next()
}
i = i.Next()
}
loaded = true
break Loop
}
if !loaded {
fmt.Fprint(os.Stderr, "Requires failed to satisfy constraints\n")
i := modules.Front()
for i != nil {
module := i.Value.(*Module)
var line strings.Builder
line.WriteString(module.name)
for val := range module.before.Iter() {
line.WriteString(fmt.Sprintf(" before: %s", val.(string)))
}
for val := range module.after.Iter() {
line.WriteString(fmt.Sprintf(" after: %s", val.(string)))
}
fmt.Fprint(os.Stderr, line.String()+"\n")
i = i.Next()
}
i = modules.Front()
Loop2:
for i != nil {
module := i.Value.(*Module)
j := i.Prev()
for j != nil {
val := j.Value.(*Module).name
if module.before.Contains(val) {
fmt.Fprintf(os.Stderr, "'%s' not before '%s'\n",
module.name, val)
break Loop2
}
j = j.Prev()
}
j = i.Next()
for j != nil {
val := j.Value.(*Module).name
if module.after.Contains(val) {
fmt.Fprintf(os.Stderr, "'%s' not after '%s'\n",
module.name, val)
break Loop2
}
j = j.Next()
}
i = i.Next()
}
os.Exit(1)
}
i := modules.Front()
for i != nil {
if !ignoreSet.Contains(i.Value.(*Module).name) {
err := i.Value.(*Module).Handler()
if err != nil {
panic(err)
}
}
i = i.Next()
}
}
================================================
FILE: rokey/cache.go
================================================
package rokey
import (
"fmt"
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
var (
cache = map[bson.ObjectID]*Rokey{}
cacheLock = sync.RWMutex{}
cacheTime = map[string]*Rokey{}
cacheTimeLock = sync.RWMutex{}
)
func GetCache(typ string, timeblock time.Time) *Rokey {
cacheTimeLock.RLock()
rkey := cacheTime[fmt.Sprintf("%s-%d", typ, timeblock.Unix())]
cacheTimeLock.RUnlock()
if rkey != nil && rkey.Type == typ {
return rkey
}
return nil
}
func GetCacheId(typ string, rkeyId bson.ObjectID) *Rokey {
cacheLock.RLock()
rkey := cache[rkeyId]
cacheLock.RUnlock()
if rkey != nil && rkey.Type == typ {
return rkey
}
return nil
}
func PutCache(rkey *Rokey) {
cacheLock.Lock()
cache[rkey.Id] = rkey
cacheLock.Unlock()
cacheTimeLock.Lock()
cacheTime[fmt.Sprintf("%s-%d", rkey.Type, rkey.Timeblock.Unix())] = rkey
cacheTimeLock.Unlock()
}
func CleanCache() {
cacheLock.Lock()
for key, rkey := range cache {
if time.Since(rkey.Timestamp) >= 721*time.Hour {
delete(cache, key)
}
}
cacheLock.Unlock()
cacheTimeLock.Lock()
for key, rkey := range cacheTime {
if time.Since(rkey.Timestamp) >= 721*time.Hour {
delete(cacheTime, key)
}
}
cacheTimeLock.Unlock()
}
func init() {
go func() {
time.Sleep(1 * time.Hour)
CleanCache()
}()
}
================================================
FILE: rokey/rokey.go
================================================
package rokey
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
type Rokey struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"_id"`
Type string `bson:"type" json:"type"`
Timeblock time.Time `bson:"timeblock" json:"timeblock"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Secret string `bson:"secret" json:"-"`
}
================================================
FILE: rokey/utils.go
================================================
package rokey
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, typ string) (rkey *Rokey, err error) {
timestamp := time.Now()
timeblock := time.Date(
timestamp.Year(),
timestamp.Month(),
timestamp.Day(),
timestamp.Hour(),
0,
0,
0,
timestamp.Location(),
)
rkey = GetCache(typ, timeblock)
if rkey != nil {
return
}
secret, err := utils.RandStr(64)
if err != nil {
return
}
coll := db.Rokeys()
rkey = &Rokey{
Type: typ,
Timeblock: timeblock,
Timestamp: timestamp,
Secret: secret,
}
err = coll.FindOneAndUpdate(
db,
&bson.M{
"type": typ,
"timeblock": timeblock,
},
&bson.M{
"$setOnInsert": rkey,
},
options.FindOneAndUpdate().
SetUpsert(true).
SetReturnDocument(options.After),
).Decode(rkey)
if err != nil {
err = database.ParseError(err)
return
}
PutCache(rkey)
return
}
func GetId(db *database.Database, typ string,
rkeyId bson.ObjectID) (rkey *Rokey, err error) {
rkey = GetCacheId(typ, rkeyId)
if rkey != nil {
return
}
coll := db.Rokeys()
rkey = &Rokey{}
err = coll.FindOneId(rkeyId, rkey)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
rkey = nil
err = nil
} else {
return
}
}
if rkey != nil {
PutCache(rkey)
if rkey.Type != typ {
rkey = nil
}
}
return
}
================================================
FILE: router/certificates.go
================================================
package router
import (
"crypto/tls"
"crypto/x509"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/sirupsen/logrus"
)
type Certificates struct {
selfCert *tls.Certificate
domainMap map[string]*tls.Certificate
wildcardMap map[string]*tls.Certificate
}
func (c *Certificates) Init() (err error) {
if c.domainMap == nil {
c.domainMap = map[string]*tls.Certificate{}
}
if c.wildcardMap == nil {
c.wildcardMap = map[string]*tls.Certificate{}
}
if c.selfCert == nil {
err = c.loadSelfCert()
if err != nil {
return
}
}
return
}
func (c *Certificates) loadSelfCert() (err error) {
certPem, keyPem, err := node.SelfCert()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server self certificate error")
return
}
keypair, err := tls.X509KeyPair(certPem, keyPem)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(
err,
"router: Failed to load self certificate",
),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server self certificate error")
return
}
c.selfCert = &keypair
return
}
func (c *Certificates) GetCertificate(info *tls.ClientHelloInfo) (
cert *tls.Certificate, err error) {
name := strings.ToLower(info.ServerName)
for len(name) > 0 && name[len(name)-1] == '.' {
name = name[:len(name)-1]
}
cert = c.domainMap[name]
if cert == nil {
index := strings.Index(name, ".")
if index > 0 {
cert = c.wildcardMap[name[index+1:]]
}
}
if cert == nil {
cert = c.selfCert
}
return
}
func (c *Certificates) Update(db *database.Database,
balncs []*balancer.Balancer) (err error) {
loaded := set.NewSet()
certificates := []*certificate.Certificate{}
nodeCerts := node.Self.Certificates
if nodeCerts != nil {
for _, certId := range nodeCerts {
if loaded.Contains(certId) {
continue
}
loaded.Add(certId)
cert, e := certificate.Get(db, certId)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
cert = nil
err = nil
} else {
return
}
}
if cert != nil {
certificates = append(certificates, cert)
}
}
}
for _, balnc := range balncs {
for _, certId := range balnc.Certificates {
cert, e := certificate.Get(db, certId)
if e != nil {
err = e
if _, ok := err.(*database.NotFoundError); ok {
cert = nil
err = nil
} else {
return
}
}
if cert != nil {
if cert.Organization != balnc.Organization ||
loaded.Contains(certId) {
continue
}
loaded.Add(certId)
certificates = append(certificates, cert)
}
}
}
domainMap := map[string]*tls.Certificate{}
wildcardMap := map[string]*tls.Certificate{}
for _, cert := range certificates {
if cert.Certificate == "" || cert.Key == "" {
continue
}
keypair, e := tls.X509KeyPair(
[]byte(cert.Certificate),
[]byte(cert.Key),
)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "router: Failed to load certificate"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server certificate error")
err = nil
continue
}
tlsCert := &keypair
x509Cert := tlsCert.Leaf
if x509Cert == nil {
var e error
x509Cert, e = x509.ParseCertificate(tlsCert.Certificate[0])
if e != nil {
continue
}
}
if len(x509Cert.Subject.CommonName) > 0 {
if strings.HasPrefix(x509Cert.Subject.CommonName, "*.") {
base := strings.Replace(
x509Cert.Subject.CommonName,
"*.", "", 1,
)
wildcardMap[base] = tlsCert
} else {
domainMap[x509Cert.Subject.CommonName] = tlsCert
}
}
for _, san := range x509Cert.DNSNames {
if strings.HasPrefix(san, "*.") {
base := strings.Replace(san, "*.", "", 1)
wildcardMap[base] = tlsCert
} else {
domainMap[san] = tlsCert
}
}
}
c.domainMap = domainMap
c.wildcardMap = wildcardMap
return
}
================================================
FILE: router/constants.go
================================================
package router
import (
"text/template"
)
const redirectConfTempl = `# pritunl-zero redirect server environment
WEB_PORT={{.WebPort}}
PUBLIC_KEY={{.PublicKey}}
KEY={{.Key}}
SECRET={{.Secret}}
`
var (
redirectConf = template.Must(
template.New("redirect").Parse(redirectConfTempl))
)
type redirectConfData struct {
WebPort int
PublicKey string
Key string
Secret string
}
================================================
FILE: router/router.go
================================================
package router
import (
"bytes"
"context"
"crypto/md5"
"crypto/tls"
"fmt"
"io"
"math/rand"
"net/http"
"path"
"strconv"
"strings"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/acme"
"github.com/pritunl/pritunl-cloud/ahandlers"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/crypto"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/proxy"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/uhandlers"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
"golang.org/x/net/http2"
"golang.org/x/net/http2/h2c"
)
var (
client = &http.Client{
Timeout: 10 * time.Second,
}
lastAlertLog = time.Time{}
)
type Router struct {
nodeHash []byte
singleType bool
adminType bool
userType bool
balancerType bool
http2 bool
port int
noRedirectServer bool
redirectSystemd bool
forceRedirectSystemd bool
protocol string
adminDomain string
userDomain string
stateLock sync.Mutex
balancers []*balancer.Balancer
certificates *Certificates
box *crypto.AsymNaclHmac
aRouter *gin.Engine
uRouter *gin.Engine
waiter *sync.WaitGroup
lock sync.Mutex
redirectServer *http.Server
redirectContext context.Context
redirectCancel context.CancelFunc
webServer *http.Server
proxy *proxy.Proxy
stop bool
}
func (r *Router) ServeHTTP(w http.ResponseWriter, re *http.Request) {
if node.Self.ForwardedProtoHeader != "" &&
strings.ToLower(re.Header.Get(
node.Self.ForwardedProtoHeader)) == "http" {
re.URL.Host = utils.StripPort(re.Host)
re.URL.Scheme = "https"
http.Redirect(w, re, re.URL.String(),
http.StatusMovedPermanently)
return
}
if r.singleType {
if r.adminType {
r.aRouter.ServeHTTP(w, re)
} else if r.userType {
r.uRouter.ServeHTTP(w, re)
} else if r.balancerType {
r.proxy.ServeHTTP(utils.StripPort(re.Host), w, re)
} else {
utils.WriteStatus(w, 520)
}
return
} else {
hst := utils.StripPort(re.Host)
if r.adminType && hst == r.adminDomain {
r.aRouter.ServeHTTP(w, re)
return
} else if r.userType && hst == r.userDomain {
r.uRouter.ServeHTTP(w, re)
return
} else if r.balancerType {
r.proxy.ServeHTTP(hst, w, re)
return
}
}
if re.URL.Path == "/check" {
utils.WriteText(w, 200, "ok")
return
}
utils.WriteStatus(w, 404)
}
func (r *Router) initRedirect() (err error) {
if r.redirectSystemd {
libPath := settings.Hypervisor.LibPath
err = utils.ExistsMkdir(libPath, 0755)
if err != nil {
return
}
redirectPth := path.Join(libPath, "redirect.conf")
r.box = &crypto.AsymNaclHmac{}
err = r.box.Generate()
if err != nil {
return
}
key := r.box.Export()
redirectOutput := &bytes.Buffer{}
redirectData := &redirectConfData{
WebPort: r.port,
PublicKey: key.PublicKey,
Key: key.Key,
Secret: key.Secret,
}
err = redirectConf.Execute(redirectOutput, redirectData)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "router: Failed to exec redirect template"),
}
return
}
err = utils.CreateWrite(
redirectPth,
redirectOutput.String(),
0600,
)
if err != nil {
return
}
}
r.redirectServer = &http.Server{
Addr: ":80",
ReadTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
IdleTimeout: 1 * time.Minute,
MaxHeaderBytes: 8192,
Handler: http.HandlerFunc(func(
w http.ResponseWriter, req *http.Request) {
if strings.HasPrefix(req.URL.Path, acme.AcmePath) {
token := acme.ParsePath(req.URL.Path)
token = utils.FilterStr(token, 96)
if token != "" {
chal, err := acme.GetChallenge(token)
if err != nil {
utils.WriteStatus(w, 400)
} else {
logrus.WithFields(logrus.Fields{
"token": token,
}).Info("router: Acme challenge requested")
utils.WriteText(w, 200, chal.Resource)
}
return
}
} else if req.URL.Path == "/check" {
utils.WriteText(w, 200, "ok")
return
}
newHost := utils.StripPort(req.Host)
if r.port != 443 {
newHost += fmt.Sprintf(":%d", r.port)
}
req.URL.Host = newHost
req.URL.Scheme = "https"
http.Redirect(w, req, req.URL.String(),
http.StatusMovedPermanently)
}),
}
return
}
func (r *Router) redirectChallengeListen(ctx context.Context) {
db := database.GetDatabase()
defer db.Close()
lst, e := event.SubscribeListener(db, []string{"acme"})
if e != nil {
select {
case <-ctx.Done():
return
default:
}
logrus.WithFields(logrus.Fields{
"error": e,
}).Error("acme: Event watch error")
return
}
sub := lst.Listen()
defer lst.Close()
for {
select {
case <-ctx.Done():
return
case msg, ok := <-sub:
if !ok {
break
}
go func() {
err := r.sendChallenge(msg.Data)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Failed to send challenge " +
"to redirect server")
}
}()
}
}
}
func (r *Router) stopRedirectSystemd() {
_, _ = commander.Exec(&commander.Opt{
Name: "systemctl",
Args: []string{
"stop",
"pritunl-cloud-redirect.service",
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
_, _ = commander.Exec(&commander.Opt{
Name: "systemctl",
Args: []string{
"stop",
"pritunl-cloud-redirect.socket",
},
Timeout: 10 * time.Second,
PipeOut: true,
PipeErr: true,
})
}
func (r *Router) startRedirectSystemd() (err error) {
r.stopRedirectSystemd()
resp, err := commander.Exec(&commander.Opt{
Name: "systemctl",
Args: []string{
"start",
"pritunl-cloud-redirect.service",
},
Timeout: 30 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
logrus.WithFields(resp.Map()).Error(
"router: Failed to start systemd redirect server")
return
}
for i := 0; i < 32; i++ {
time.Sleep(250 * time.Millisecond)
resp, err = commander.Exec(&commander.Opt{
Name: "systemctl",
Args: []string{
"is-active",
"pritunl-cloud-redirect.service",
},
Timeout: 5 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err == nil {
return
}
}
r.stopRedirectSystemd()
err = &errortypes.ExecError{
errors.New("router: Timeout on systemd redirect server"),
}
return
}
func (r *Router) startRedirect() {
defer r.waiter.Done()
if r.port == 80 || r.noRedirectServer {
return
}
if r.redirectSystemd {
defer r.stopRedirectSystemd()
err := r.startRedirectSystemd()
if err != nil {
if r.forceRedirectSystemd {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Failed to start systemd redirect server")
return
} else {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Falling back to main process redirect server")
}
} else {
logrus.WithFields(logrus.Fields{
"production": constants.Production,
"protocol": "http",
"port": 80,
}).Info("router: Started systemd redirect server")
ctx, cancel := context.WithCancel(context.Background())
r.redirectContext = ctx
r.redirectCancel = cancel
for {
r.redirectChallengeListen(ctx)
select {
case <-ctx.Done():
return
default:
}
}
}
}
r.stopRedirectSystemd()
logrus.WithFields(logrus.Fields{
"production": constants.Production,
"protocol": "http",
"port": 80,
}).Error("router: Starting fallback main process redirect server")
err := r.redirectServer.ListenAndServe()
if err != nil {
if err == http.ErrServerClosed {
err = nil
} else {
err = &errortypes.UnknownError{
errors.Wrap(err, "router: Server listen failed"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Redirect server error")
}
}
}
func (r *Router) sendChallenge(chal any) (err error) {
encData, err := r.box.SealJson(chal)
if err != nil {
return
}
req, err := http.NewRequest(
"POST",
"http://127.0.0.1:80/token",
bytes.NewReader([]byte(encData)),
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "acme: Redirect token request failed"),
}
return
}
resp, err := client.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "acme: Redirect token request failed"),
}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
logrus.WithFields(logrus.Fields{
"status_code": resp.StatusCode,
}).Error("acme: Redirect request bad status")
return
}
return
}
func (r *Router) initWeb() (err error) {
r.adminType = node.Self.IsAdmin()
r.userType = node.Self.IsUser()
r.balancerType = node.Self.IsBalancer()
r.adminDomain = node.Self.AdminDomain
r.userDomain = node.Self.UserDomain
r.http2 = node.Self.Http2
r.noRedirectServer = node.Self.NoRedirectServer
r.redirectSystemd = utils.IsSystemd() ||
settings.Router.ForceRedirectSystemd
r.forceRedirectSystemd = settings.Router.ForceRedirectSystemd
if r.adminType && !r.userType && !r.balancerType {
r.singleType = true
} else if r.userType && !r.balancerType && !r.adminType {
r.singleType = true
} else if r.balancerType && !r.adminType && !r.userType {
r.singleType = true
} else {
r.singleType = false
}
r.port = node.Self.Port
if r.port == 0 {
r.port = 443
}
r.protocol = node.Self.Protocol
if r.protocol == "" {
r.protocol = "https"
}
if r.adminType {
r.aRouter = gin.New()
if constants.DebugWeb {
r.aRouter.Use(gin.Logger())
}
ahandlers.Register(r.aRouter)
}
if r.userType {
r.uRouter = gin.New()
if constants.DebugWeb {
r.uRouter.Use(gin.Logger())
}
uhandlers.Register(r.uRouter)
}
readTimeout := time.Duration(settings.Router.ReadTimeout) * time.Second
readHeaderTimeout := time.Duration(
settings.Router.ReadHeaderTimeout) * time.Second
writeTimeout := time.Duration(settings.Router.WriteTimeout) * time.Second
idleTimeout := time.Duration(settings.Router.IdleTimeout) * time.Second
r.webServer = &http.Server{
Addr: fmt.Sprintf(":%d", r.port),
Handler: r,
ReadTimeout: readTimeout,
ReadHeaderTimeout: readHeaderTimeout,
WriteTimeout: writeTimeout,
IdleTimeout: idleTimeout,
MaxHeaderBytes: settings.Router.MaxHeaderBytes,
}
if !r.http2 {
r.webServer.TLSNextProto = make(map[string]func(
*http.Server, *tls.Conn, http.Handler))
}
if r.http2 && r.protocol == "http" {
h2s := &http2.Server{
IdleTimeout: idleTimeout,
ReadIdleTimeout: readTimeout,
}
r.webServer.Handler = h2c.NewHandler(r, h2s)
}
return
}
func (r *Router) startWeb() {
defer r.waiter.Done()
logrus.WithFields(logrus.Fields{
"production": constants.Production,
"protocol": r.protocol,
"port": r.port,
"http2": r.http2,
"read_timeout": settings.Router.ReadTimeout,
"write_timeout": settings.Router.WriteTimeout,
"idle_timeout": settings.Router.IdleTimeout,
"read_header_timeout": settings.Router.ReadHeaderTimeout,
}).Info("router: Starting web server")
if r.protocol == "http" {
err := r.webServer.ListenAndServe()
if err != nil {
if err == http.ErrServerClosed {
err = nil
} else {
err = &errortypes.UnknownError{
errors.Wrap(err, "router: Server listen failed"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server error")
return
}
}
} else {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
CipherSuites: []uint16{
tls.TLS_AES_128_GCM_SHA256, // 0x1301
tls.TLS_AES_256_GCM_SHA384, // 0x1302
tls.TLS_CHACHA20_POLY1305_SHA256, // 0x1303
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, // 0xc02b
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, // 0xc02f
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, // 0xc02c
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, // 0xc030
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, // 0xcca9
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, // 0xcca8
},
GetCertificate: r.certificates.GetCertificate,
}
if r.http2 {
tlsConfig.NextProtos = []string{"h2"}
}
listener, err := tls.Listen("tcp", r.webServer.Addr, tlsConfig)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "router: TLS listen failed"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server TLS error")
return
}
err = r.webServer.Serve(listener)
if err != nil {
if err == http.ErrServerClosed {
err = nil
} else {
err = &errortypes.UnknownError{
errors.Wrap(err, "router: Server listen failed"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Web server error")
return
}
}
}
return
}
func (r *Router) initServers() (err error) {
r.lock.Lock()
defer r.lock.Unlock()
err = r.certificates.Init()
if err != nil {
return
}
err = r.updateState()
if err != nil {
return
}
err = r.initWeb()
if err != nil {
return
}
err = r.initRedirect()
if err != nil {
return
}
return
}
func (r *Router) startServers() {
r.lock.Lock()
defer r.lock.Unlock()
if r.webServer == nil {
return
}
if !r.redirectSystemd && r.redirectServer == nil {
return
}
r.waiter.Add(2)
go r.startRedirect()
go r.startWeb()
time.Sleep(250 * time.Millisecond)
return
}
func (r *Router) Restart() {
r.lock.Lock()
defer r.lock.Unlock()
if r.redirectServer != nil {
redirectCtx, redirectCancel := context.WithTimeout(
context.Background(),
1*time.Second,
)
defer redirectCancel()
r.redirectServer.Shutdown(redirectCtx)
}
if r.webServer != nil {
webCtx, webCancel := context.WithTimeout(
context.Background(),
1*time.Second,
)
defer webCancel()
r.webServer.Shutdown(webCtx)
}
func() {
defer func() {
recover()
}()
if r.redirectServer != nil {
r.redirectServer.Close()
}
if r.webServer != nil {
r.webServer.Close()
}
if r.redirectCancel != nil {
r.redirectCancel()
}
}()
event.WebSocketsStop()
r.redirectServer = nil
r.webServer = nil
time.Sleep(250 * time.Millisecond)
}
func (r *Router) Shutdown() {
r.stop = true
r.Restart()
time.Sleep(1 * time.Second)
r.Restart()
time.Sleep(1 * time.Second)
r.Restart()
}
func (r *Router) hashNode() []byte {
hash := md5.New()
for _, typ := range node.Self.Types {
io.WriteString(hash, typ)
}
io.WriteString(hash, node.Self.AdminDomain)
io.WriteString(hash, node.Self.UserDomain)
io.WriteString(hash, strconv.Itoa(node.Self.Port))
io.WriteString(hash, fmt.Sprintf("%t", node.Self.NoRedirectServer))
io.WriteString(hash, fmt.Sprintf("%t", node.Self.Http2))
io.WriteString(hash, node.Self.Protocol)
io.WriteString(hash, strconv.Itoa(settings.Router.ReadTimeout))
io.WriteString(hash, strconv.Itoa(settings.Router.ReadHeaderTimeout))
io.WriteString(hash, strconv.Itoa(settings.Router.WriteTimeout))
io.WriteString(hash, strconv.Itoa(settings.Router.IdleTimeout))
io.WriteString(hash, strconv.Itoa(settings.Router.MaxHeaderBytes))
io.WriteString(hash, strconv.FormatBool(
utils.IsSystemd() || settings.Router.ForceRedirectSystemd))
io.WriteString(hash, strconv.FormatBool(
settings.Router.ForceRedirectSystemd))
return hash.Sum(nil)
}
func (r *Router) watchNode() {
for {
time.Sleep(1 * time.Second)
if settings.Local.DisableWeb {
r.Restart()
continue
}
hash := r.hashNode()
if bytes.Compare(r.nodeHash, hash) != 0 {
r.nodeHash = hash
time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
r.Restart()
time.Sleep(2 * time.Second)
}
}
}
func (r *Router) refreshResolver() {
db := database.GetDatabase()
defer db.Close()
err := proxy.ResolverRefresh(db)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("proxy: Failed to load proxy state")
}
}
func (r *Router) watchResolver() {
for {
time.Sleep(time.Duration(
settings.Router.ProxyResolverRefresh) * time.Second)
if node.Self.IsBalancer() {
r.refreshResolver()
}
}
}
func (r *Router) updateState() (err error) {
db := database.GetDatabase()
defer db.Close()
if node.Self.IsBalancer() {
dcId, e := node.Self.GetDatacenter(db)
if e != nil {
err = e
return
}
balncs, e := balancer.GetAll(db, &bson.M{
"datacenter": dcId,
})
if e != nil {
r.balancers = []*balancer.Balancer{}
return
}
r.balancers = balncs
} else {
r.balancers = []*balancer.Balancer{}
}
r.stateLock.Lock()
defer r.stateLock.Unlock()
err = r.certificates.Update(db, r.balancers)
if err != nil {
return
}
err = r.proxy.Update(db, r.balancers)
if err != nil {
return
}
return
}
func (r *Router) watchState() {
for {
time.Sleep(4 * time.Second)
err := r.updateState()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("proxy: Failed to load proxy state")
}
}
}
func (r *Router) Run() (err error) {
r.nodeHash = r.hashNode()
go r.watchNode()
go r.watchState()
go r.watchResolver()
for {
if settings.Local.DisableWeb {
if time.Since(lastAlertLog) > 3*time.Minute {
lastAlertLog = time.Now()
logrus.WithFields(logrus.Fields{
"message": settings.Local.DisableMsg,
}).Error("router: Web server disabled from vulnerability alert")
}
time.Sleep(1 * time.Second)
continue
}
if !node.Self.IsAdmin() && !node.Self.IsUser() &&
!node.Self.IsBalancer() {
time.Sleep(500 * time.Millisecond)
continue
}
r.refreshResolver()
err = r.initServers()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("router: Failed to init web servers")
time.Sleep(1 * time.Second)
continue
}
r.waiter = &sync.WaitGroup{}
r.startServers()
r.waiter.Wait()
if r.stop {
break
}
}
return
}
func (r *Router) Init() {
if constants.DebugWeb {
gin.SetMode(gin.DebugMode)
} else {
gin.SetMode(gin.ReleaseMode)
}
r.certificates = &Certificates{}
r.proxy = &proxy.Proxy{}
r.proxy.Init()
}
================================================
FILE: scheduler/constants.go
================================================
package scheduler
const (
UnitKind = "unit"
InstanceUnitKind = "unit-instance"
OffsetCount = 3
OffsetInit = 15
OffsetInc = 10
)
================================================
FILE: scheduler/scheduler.go
================================================
package scheduler
import (
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
)
type Scheduler struct {
Id bson.ObjectID `bson:"_id" json:"id"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Pod bson.ObjectID `bson:"pod" json:"pod"`
Kind string `bson:"kind" json:"kind"`
Created time.Time `bson:"created" json:"created"`
Modified time.Time `bson:"modified" json:"modified"`
Count int `bson:"count" json:"count"`
Spec bson.ObjectID `bson:"spec" json:"spec"`
OverrideCount int `bson:"override_count" json:"override_count"`
Consumed int `bson:"consumed" json:"consumed"`
Tickets TicketsStore `bson:"tickets" json:"tickets"`
Failures map[bson.ObjectID]int `bson:"failures" json:"failures"`
}
type Ticket struct {
Node bson.ObjectID `bson:"n" json:"n"`
Offset int `bson:"t" json:"t"`
}
type TicketsStore map[bson.ObjectID][]*Ticket
func (s *Scheduler) Refresh(db *database.Database) (exists bool, err error) {
coll := db.Schedulers()
schd := &Scheduler{}
err = coll.FindOne(db, bson.M{
"_id": s.Id,
}, database.FindOneProject(
"count",
"consumed",
"tickets",
"failures",
)).Decode(schd)
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
return
}
exists = true
s.Count = schd.Count
s.Consumed = schd.Consumed
s.Tickets = schd.Tickets
s.Failures = schd.Failures
return
}
func (s *Scheduler) ClearTickets(db *database.Database) (err error) {
coll := db.Schedulers()
schd := &Scheduler{}
err = coll.FindOneAndUpdate(db, bson.M{
"_id": s.Id,
}, bson.M{
"$unset": bson.M{
"tickets." + node.Self.Id.Hex(): "",
},
}, options.FindOneAndUpdate().SetReturnDocument(
options.After)).Decode(schd)
if err != nil {
err = database.ParseError(err)
return
}
s.Count = schd.Count
s.Consumed = schd.Consumed
s.Tickets = schd.Tickets
s.Failures = schd.Failures
return
}
func (s *Scheduler) Failure(db *database.Database) (limit bool, err error) {
coll := db.Schedulers()
schd := &Scheduler{}
if s.Failures == nil {
s.Failures = map[bson.ObjectID]int{}
}
s.Failures[node.Self.Id] += 1
update := bson.M{
"$inc": bson.M{
"failures." + node.Self.Id.Hex(): 1,
},
}
if s.Failures[node.Self.Id] >= settings.Hypervisor.MaxDeploymentFailures {
limit = true
update["$unset"] = bson.M{
"tickets." + node.Self.Id.Hex(): "",
}
}
err = coll.FindOneAndUpdate(db, bson.M{
"_id": s.Id,
}, update, options.FindOneAndUpdate().SetReturnDocument(
options.After)).Decode(schd)
if err != nil {
err = database.ParseError(err)
return
}
s.Count = schd.Count
s.Consumed = schd.Consumed
s.Tickets = schd.Tickets
s.Failures = schd.Failures
return
}
func (s *Scheduler) Ready() bool {
if s.Failures == nil {
return true
}
return s.Failures[node.Self.Id] < settings.Hypervisor.MaxDeploymentFailures
}
func (s *Scheduler) Consume(db *database.Database) (err error) {
coll := db.Schedulers()
schd := &Scheduler{}
err = coll.FindOneAndUpdate(db, bson.M{
"_id": s.Id,
"$expr": bson.M{
"$lt": []interface{}{"$consumed", "$count"},
},
}, bson.M{
"$set": bson.M{
"modified": time.Now(),
},
"$inc": bson.M{
"consumed": 1,
},
}, options.FindOneAndUpdate().SetReturnDocument(
options.After)).Decode(schd)
if err != nil {
err = database.ParseError(err)
return
}
s.Count = schd.Count
s.Consumed = schd.Consumed
s.Failures = schd.Failures
return
}
func (s *Scheduler) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
return
}
func (s *Scheduler) Commit(db *database.Database) (err error) {
coll := db.Schedulers()
err = coll.Commit(s.Id, s)
if err != nil {
return
}
return
}
func (s *Scheduler) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Schedulers()
err = coll.CommitFields(s.Id, s, fields)
if err != nil {
return
}
return
}
func (s *Scheduler) Insert(db *database.Database) (err error) {
coll := db.Schedulers()
_, err = coll.InsertOne(db, s)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: scheduler/unit.go
================================================
package scheduler
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/sirupsen/logrus"
)
type InstanceUnit struct {
unit *unit.Unit
spec *spec.Spec
count int
nodes spec.Nodes
}
func (u *InstanceUnit) Schedule(db *database.Database, count int) (err error) {
if u.unit.Kind != deployment.Instance && u.unit.Kind != deployment.Image {
err = &errortypes.ParseError{
errors.New("scheduler: Invalid unit kind"),
}
return
}
if u.spec.Instance == nil {
err = &errortypes.ParseError{
errors.New("scheduler: Missing instance data"),
}
return
}
if u.spec.Instance.Shape.IsZero() && u.spec.Instance.Node.IsZero() {
err = &errortypes.ParseError{
errors.New("scheduler: Missing shape or node"),
}
return
}
overrideCount := 0
if count == 0 {
u.count = u.unit.Count - len(u.unit.Deployments)
} else {
u.count = count
overrideCount = len(u.unit.Deployments) + count
}
schd := &Scheduler{
Id: u.unit.Id,
Organization: u.unit.Organization,
Pod: u.unit.Pod,
Kind: InstanceUnitKind,
Spec: u.spec.Id,
Count: u.count,
OverrideCount: overrideCount,
Failures: map[bson.ObjectID]int{},
}
if !u.spec.Instance.Node.IsZero() {
nde, e := node.Get(db, u.spec.Instance.Node)
if e != nil {
err = e
return
}
u.nodes = []*node.Node{nde}
} else {
ndes, offlineCount, noMountCount, e := u.spec.GetAllNodes(db)
if e != nil {
err = e
return
}
u.nodes = ndes
if len(u.nodes) == 0 {
logrus.WithFields(logrus.Fields{
"unit": u.unit.Id.Hex(),
"shape": u.spec.Instance.Shape.Hex(),
"offline_count": offlineCount,
"missing_mount_count": noMountCount,
}).Error("scheduler: Failed to find nodes to schedule")
return
}
}
if u.count == 0 {
err = &errortypes.ParseError{
errors.New("scheduler: Cannot schedule zero count unit"),
}
return
}
primaryNodes, backupNodes := u.processNodes(u.nodes)
var tickets TicketsStore
if u.count < len(primaryNodes) {
tickets, err = u.scheduleSimple(db, primaryNodes, backupNodes)
if err != nil {
return
}
} else {
tickets, err = u.scheduleComplex(db, primaryNodes, backupNodes)
if err != nil {
return
}
}
schd.Tickets = tickets
schd.Created = time.Now()
schd.Modified = time.Now()
logrus.WithFields(logrus.Fields{
"unit": u.unit.Id.Hex(),
"count": u.count,
"primary_nodes": len(primaryNodes),
"backup_nodes": len(backupNodes),
"tickets": len(tickets),
}).Info("scheduler: Scheduling unit")
err = schd.Insert(db)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
return
}
return
}
func (u *InstanceUnit) processNodes(nodes spec.Nodes) (
primaryNodes, backupNodes spec.Nodes) {
nodes.Sort()
for _, nde := range nodes {
if nde.SizeResource(u.spec.Instance.Memory,
u.spec.Instance.Processors) {
primaryNodes = append(primaryNodes, nde)
} else {
backupNodes = append(backupNodes, nde)
}
}
return
}
func (u *InstanceUnit) scheduleSimple(db *database.Database,
primaryNodes, backupNodes spec.Nodes) (tickets TicketsStore, err error) {
tickets = TicketsStore{}
count := u.count
offset := 0
for _, nde := range primaryNodes {
if count <= 0 {
count = u.count
if offset == 0 {
offset += OffsetInit
} else {
offset += OffsetInc
}
}
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
}
for _, nde := range backupNodes {
if count <= 0 {
count = u.count
if offset == 0 {
offset += OffsetInit
} else {
offset += OffsetInc
}
}
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
}
return
}
func (u *InstanceUnit) scheduleComplex(db *database.Database,
primaryNodes, backupNodes spec.Nodes) (tickets TicketsStore, err error) {
tickets = TicketsStore{}
count := u.count
offset := 0
overscheduled := 0
if primaryNodes.Len() != 0 {
for _, nde := range primaryNodes {
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
nde.CpuUnitsRes += u.spec.Instance.Processors
nde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()
if count <= 0 {
break
}
}
} else {
for _, nde := range backupNodes {
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
overscheduled += 1
nde.CpuUnitsRes += u.spec.Instance.Processors
nde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()
if count <= 0 {
break
}
}
}
for i := 0; i < OffsetCount; i++ {
attempts := 0
for attempts = 0; attempts < 100; attempts++ {
if count <= 0 {
break
}
for {
primaryNodes, _ = u.processNodes(u.nodes)
if primaryNodes.Len() == 0 {
break
}
for _, nde := range primaryNodes {
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
nde.CpuUnitsRes += u.spec.Instance.Processors
nde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()
break
}
if count <= 0 {
break
}
}
if count <= 0 {
break
}
for {
_, backupNodes = u.processNodes(u.nodes)
if backupNodes.Len() == 0 {
break
}
for _, nde := range backupNodes {
tickets[nde.Id] = append(tickets[nde.Id], &Ticket{
Node: nde.Id,
Offset: offset,
})
count -= 1
if i == 0 {
overscheduled += 1
}
nde.CpuUnitsRes += u.spec.Instance.Processors
nde.MemoryUnitsRes += u.spec.Instance.MemoryUnits()
break
}
if count <= 0 {
break
}
}
}
if count != 0 {
err = &errortypes.ParseError{
errors.Newf("schedule: Count %d remaining after %d "+
"complex schedule attempts", count, attempts),
}
return
}
count = u.count
if offset == 0 {
offset += OffsetInit
} else {
offset += OffsetInc
}
}
if overscheduled > 0 {
logrus.WithFields(logrus.Fields{
"unit": u.unit.Id.Hex(),
"kind": u.unit.Kind,
"shape": u.spec.Instance.Shape.Hex(),
"overscheduled": overscheduled,
}).Info("scheduler: Overscheduled unit")
}
return
}
func NewInstanceUnit(unt *unit.Unit, spc *spec.Spec) (
instUnit *InstanceUnit) {
instUnit = &InstanceUnit{
unit: unt,
spec: spc,
}
return
}
================================================
FILE: scheduler/utils.go
================================================
package scheduler
import (
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
)
func Exists(db *database.Database, schdId bson.ObjectID) (
exists bool, err error) {
coll := db.Schedulers()
schd := &Scheduler{}
err = coll.FindOneId(schdId, schd)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
return
}
exists = true
return
}
func Get(db *database.Database, schdId bson.ObjectID) (
schd *Scheduler, err error) {
coll := db.Schedulers()
schd = &Scheduler{}
err = coll.FindOneId(schdId, schd)
if err != nil {
return
}
return
}
func GetAll(db *database.Database) (schds []*Scheduler, err error) {
coll := db.Schedulers()
schds = []*Scheduler{}
cursor, err := coll.Find(db, bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
schd := &Scheduler{}
err = cursor.Decode(schd)
if err != nil {
err = database.ParseError(err)
return
}
schds = append(schds, schd)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllActive(db *database.Database) (schds []*Scheduler, err error) {
coll := db.Schedulers()
schds = []*Scheduler{}
cursor, err := coll.Find(db, bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
schd := &Scheduler{}
err = cursor.Decode(schd)
if err != nil {
err = database.ParseError(err)
return
}
if schd.Consumed < schd.Count {
schds = append(schds, schd)
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, schdId bson.ObjectID) (
deleted bool, err error) {
coll := db.Schedulers()
resp, err := coll.DeleteOne(db, &bson.M{
"_id": schdId,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if resp.DeletedCount > 0 {
deleted = true
}
return
}
func Schedule(db *database.Database, unt *unit.Unit) (err error) {
exists, e := Exists(db, unt.Id)
if e != nil {
err = e
return
}
if exists {
return
}
spc, err := spec.Get(db, unt.DeploySpec)
if err != nil {
return
}
errData, err := spc.Refresh(db)
if err != nil {
return
}
if errData != nil {
err = errData.GetError()
return
}
switch unt.Kind {
case deployment.Instance, deployment.Image:
schd := NewInstanceUnit(unt, spc)
err = schd.Schedule(db, 0)
if err != nil {
return
}
}
return
}
func ManualSchedule(db *database.Database, unt *unit.Unit,
specId bson.ObjectID, count int) (
errData *errortypes.ErrorData, err error) {
exists, e := Exists(db, unt.Id)
if e != nil {
err = e
return
}
if exists {
errData = &errortypes.ErrorData{
Error: "scheduler_active",
Message: "Cannot schedule deployments while scheduler is active",
}
return
}
if specId.IsZero() {
specId = unt.DeploySpec
}
spc, err := spec.Get(db, specId)
if err != nil {
return
}
errData, err = spc.Refresh(db)
if err != nil {
return
}
if errData != nil {
return
}
if spc.Unit != unt.Id {
errData = &errortypes.ErrorData{
Error: "unit_deploy_spec_invalid",
Message: "Invalid unit deployment commit",
}
return
}
switch unt.Kind {
case deployment.Instance, deployment.Image:
if unt.Kind == deployment.Image {
count = 1
}
schd := NewInstanceUnit(unt, spc)
err = schd.Schedule(db, count)
if err != nil {
return
}
default:
err = &errortypes.ParseError{
errors.Newf("scheduler: Unknown unit kind %s", unt.Kind),
}
return
}
return
}
================================================
FILE: secondary/constants.go
================================================
package secondary
import "github.com/pritunl/mongo-go-driver/v2/bson"
const (
Duo = "duo"
OneLogin = "one_login"
Okta = "okta"
Push = "push"
Phone = "phone"
Passcode = "passcode"
Sms = "sms"
Admin = "admin"
AdminDevice = "admin_device"
AdminDeviceRegister = "admin_device_register"
User = "user"
UserDevice = "user_device"
UserDeviceRegister = "user_device_register"
UserManage = "user_manage"
UserManageDevice = "user_manage_device"
UserManageDeviceRegister = "user_manage_device_register"
)
var (
DeviceProvider, _ = bson.ObjectIDFromHex("100000000000000000000000")
)
================================================
FILE: secondary/duo.go
================================================
package secondary
import (
"encoding/json"
"net/http"
"net/url"
"github.com/sirupsen/logrus"
"github.com/dropbox/godropbox/errors"
duoapi "github.com/duosecurity/duo_api_golang"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
)
type duoApiResp struct {
Result string `json:"result"`
Status string `json:"status"`
StatusMsg string `json:"status_msg"`
}
type duoApi struct {
Stat string `json:"stat"`
Code int `json:"code"`
Message string `json:"message"`
Response duoApiResp `json:"response"`
}
func duo(db *database.Database, provider *settings.SecondaryProvider,
r *http.Request, usr *user.User, factor, passcode string) (
result bool, err error) {
if factor == Passcode && passcode == "" {
err = &errortypes.AuthenticationError{
errors.New("secondary: Duo passcode empty"),
}
return
}
api := duoapi.NewDuoApi(
provider.DuoKey,
provider.DuoSecret,
provider.DuoHostname,
"pritunl-cloud",
)
query := url.Values{}
query.Set("username", usr.Username)
query.Set("ipaddr", node.Self.GetRemoteAddr(r))
switch factor {
case Push:
query.Set("factor", "push")
query.Set("device", "auto")
break
case Phone:
query.Set("factor", "phone")
query.Set("device", "auto")
break
case Passcode:
query.Set("factor", "passcode")
query.Set("passcode", passcode)
break
case Sms:
query.Set("factor", "sms")
query.Set("device", "auto")
break
}
resp, data, err := api.SignedCall(
"POST",
"/auth/v2/auth",
query,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Duo auth request failed"),
}
return
}
if data == nil {
err = &errortypes.RequestError{
errors.Newf(
"secondary: Duo auth request failed %d",
resp.StatusCode,
),
}
return
}
duoData := &duoApi{}
err = json.Unmarshal(data, duoData)
if err != nil {
err = &errortypes.ParseError{
errors.Wrapf(
err,
"secondary: Failed to parse Duo response %d",
resp.StatusCode,
),
}
return
}
if resp.StatusCode != 200 {
logrus.WithFields(logrus.Fields{
"username": usr.Username,
"status_code": resp.StatusCode,
"duo_factor": factor,
"duo_stat": duoData.Stat,
"duo_code": duoData.Code,
"duo_msg": duoData.Message,
"duo_result": duoData.Response.Result,
"duo_status": duoData.Response.Status,
"duo_status_msg": duoData.Response.StatusMsg,
}).Error("secondary: Duo auth request failed")
err = &errortypes.RequestError{
errors.New("secondary: Duo auth request failed"),
}
}
switch duoData.Response.Result {
case "allow":
err = audit.New(
db,
r,
usr.Id,
audit.DuoApprove,
audit.Fields{
"duo_factor": factor,
},
)
if err != nil {
return
}
result = true
break
case "deny":
if factor != Sms {
err = audit.New(
db,
r,
usr.Id,
audit.DuoDeny,
audit.Fields{
"duo_factor": factor,
"duo_status": duoData.Response.Status,
"duo_status_msg": duoData.Response.StatusMsg,
},
)
if err != nil {
return
}
}
break
default:
logrus.WithFields(logrus.Fields{
"username": usr.Username,
"status_code": resp.StatusCode,
"duo_factor": factor,
"duo_stat": duoData.Stat,
"duo_code": duoData.Code,
"duo_msg": duoData.Message,
"duo_result": duoData.Response.Result,
"duo_status": duoData.Response.Status,
"duo_status_msg": duoData.Response.StatusMsg,
}).Error("secondary: Duo auth request unknown")
err = &errortypes.RequestError{
errors.New("secondary: Duo auth request unknown"),
}
return
}
return
}
================================================
FILE: secondary/errors.go
================================================
package secondary
import (
"github.com/dropbox/godropbox/errors"
)
type IncompleteError struct {
errors.DropboxError
}
================================================
FILE: secondary/okta.go
================================================
package secondary
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
oktaClient = &http.Client{
Timeout: 20 * time.Second,
}
)
type oktaProfile struct {
Email string `json:"email"`
Login string `json:"login"`
}
type oktaUser struct {
Id string `json:"id"`
Status string `json:"status"`
Profile oktaProfile `json:"profile"`
}
type oktaFactor struct {
Id string `json:"id"`
FactorType string `json:"factorType"`
Provider string `json:"provider"`
Status string `json:"status"`
}
type oktaVerifyParams struct {
Passcode string `json:"passCode,omitempty"`
}
type oktaLink struct {
Href string `json:"href"`
}
type oktaLinks struct {
Poll oktaLink `json:"poll"`
}
type oktaVerify struct {
FactorResult string `json:"factorResult"`
Links oktaLinks `json:"_links"`
}
func okta(db *database.Database, provider *settings.SecondaryProvider,
r *http.Request, usr *user.User, factor, passcode string) (
result bool, err error) {
if factor != Push && factor != Passcode {
err = &errortypes.UnknownError{
errors.New("secondary: Okta invalid factor"),
}
return
}
if factor == Passcode && passcode == "" {
err = &errortypes.AuthenticationError{
errors.New("secondary: Okta passcode empty"),
}
return
}
apiUrl := fmt.Sprintf(
"https://%s",
provider.OktaDomain,
)
apiHeader := fmt.Sprintf(
"SSWS %s",
provider.OktaToken,
)
reqUrl, _ := url.Parse(apiUrl + "/api/v1/users/" + usr.Username)
req, err := http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta users request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err := oktaClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta users request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: Okta request error")
if err != nil {
return
}
oktaUsr := &oktaUser{}
err = json.NewDecoder(resp.Body).Decode(oktaUsr)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: Okta users parse failed"),
}
return
}
shortUsername := ""
if oktaUsr.Id == "" && strings.Contains(usr.Username, "@") {
shortUsername = strings.SplitN(usr.Username, "@", 2)[0]
reqUrl, _ = url.Parse(apiUrl + "/api/v1/users/" + shortUsername)
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta users request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oktaClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta users request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: Okta request error")
if err != nil {
return
}
oktaUsr = &oktaUser{}
err = json.NewDecoder(resp.Body).Decode(oktaUsr)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: Okta users parse failed"),
}
return
}
}
if oktaUsr.Id == "" {
err = &errortypes.NotFoundError{
errors.New("secondary: Okta users not found"),
}
return
}
if usr.Username != oktaUsr.Profile.Login &&
usr.Username != oktaUsr.Profile.Email &&
(shortUsername != "" && shortUsername != oktaUsr.Profile.Login) {
err = &errortypes.AuthenticationError{
errors.New("secondary: Okta username mismatch"),
}
return
}
if strings.ToLower(oktaUsr.Status) != "active" {
err = &errortypes.AuthenticationError{
errors.New("secondary: Okta user is not active"),
}
return
}
userId := oktaUsr.Id
reqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(
"/api/v1/users/%s/factors", userId))
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta factors request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oktaClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta factors request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: Okta request error")
if err != nil {
return
}
factors := []*oktaFactor{}
err = json.NewDecoder(resp.Body).Decode(&factors)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: Okta factors parse failed"),
}
return
}
if len(factors) == 0 {
err = &errortypes.NotFoundError{
errors.New("secondary: Okta user has no factors"),
}
return
}
factorId := ""
for _, fctr := range factors {
if fctr.Id == "" {
continue
}
if strings.ToLower(fctr.Status) != "active" ||
strings.ToLower(fctr.Provider) != "okta" {
continue
}
switch factor {
case Push:
if strings.ToLower(fctr.FactorType) != "push" {
continue
}
break
case Passcode:
if strings.ToLower(fctr.FactorType) != "token:software:totp" {
continue
}
break
default:
continue
}
factorId = fctr.Id
}
verifyParams := &oktaVerifyParams{
Passcode: passcode,
}
verifyBody, err := json.Marshal(verifyParams)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(
err, "secondary: Okta failed to parse verify params"),
}
return
}
reqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(
"/api/v1/users/%s/factors/%s/verify", userId, factorId))
req, err = http.NewRequest(
"POST",
reqUrl.String(),
bytes.NewBuffer(verifyBody),
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta verify request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", apiHeader)
req.Header.Set("User-Agent", r.UserAgent())
req.Header.Set("X-Forwarded-For", node.Self.GetRemoteAddr(r))
resp, err = oktaClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta verify request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequestN(
resp, "secondary: Okta request error",
[]int{200, 201},
)
if err != nil {
return
}
verify := &oktaVerify{}
err = json.NewDecoder(resp.Body).Decode(verify)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: Okta verify parse failed"),
}
return
}
if strings.ToLower(verify.FactorResult) == "waiting" &&
verify.Links.Poll.Href != "" {
start := time.Now()
for {
if time.Now().Sub(start) > 45*time.Second {
err = audit.New(
db,
r,
usr.Id,
audit.OktaDeny,
audit.Fields{
"okta_factor": factor,
"okta_error": "timeout",
},
)
if err != nil {
return
}
result = false
return
}
reqUrl, _ = url.Parse(verify.Links.Poll.Href)
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta verify request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
req.Header.Set("User-Agent", r.UserAgent())
req.Header.Set("X-Forwarded-For", node.Self.GetRemoteAddr(r))
resp, err = oktaClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: Okta verify request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequestN(
resp, "secondary: Okta request error",
[]int{200, 201},
)
if err != nil {
return
}
verify = &oktaVerify{}
err = json.NewDecoder(resp.Body).Decode(verify)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: Okta verify parse failed"),
}
return
}
if strings.ToLower(verify.FactorResult) == "waiting" &&
verify.Links.Poll.Href != "" {
continue
}
break
}
}
if strings.ToLower(verify.FactorResult) == "success" {
err = audit.New(
db,
r,
usr.Id,
audit.OktaApprove,
audit.Fields{
"okta_factor": factor,
},
)
if err != nil {
return
}
result = true
} else {
err = audit.New(
db,
r,
usr.Id,
audit.OktaDeny,
audit.Fields{
"okta_factor": factor,
},
)
if err != nil {
return
}
result = false
}
return
}
================================================
FILE: secondary/onelogin.go
================================================
package secondary
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
oneloginClient = &http.Client{
Timeout: 20 * time.Second,
}
)
type oneloginAuthParams struct {
GrantType string `json:"grant_type"`
}
type oneloginAuth struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
TokenType string `json:"token_type"`
}
type oneloginUsersData struct {
Id int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Status int `json:"status"`
}
type oneloginUsers struct {
Data []oneloginUsersData `json:"data"`
}
type oneloginOtpDevicesDataDevices struct {
Id int `json:"id"`
TypeDisplayName string `json:"type_display_name"`
UserDisplayName string `json:"user_display_name"`
AuthFactorName string `json:"auth_factor_name"`
Active bool `json:"boolean"`
Default bool `json:"default"`
NeedsTrigger bool `json:"needs_trigger"`
}
type oneloginOtpDevicesData struct {
OtpDevices []oneloginOtpDevicesDataDevices `json:"otp_devices"`
}
type oneloginOtpDevices struct {
Data oneloginOtpDevicesData `json:"data"`
}
type oneloginActivateParams struct {
IpAddr string `json:"ipaddr"`
}
type oneloginActivateData struct {
Id int `json:"id"`
DeviceId int `json:"device_id"`
StateToken string `json:"state_token"`
}
type oneloginActivate struct {
Data []oneloginActivateData `json:"data"`
}
type oneloginVerifyParams struct {
OtpToken string `json:"otp_token,omitempty"`
StateToken string `json:"state_token,omitempty"`
}
type oneloginVerifyStatus struct {
Type string `json:"type"`
Code int `json:"code"`
Message string `json:"message"`
Error bool `json:"error"`
}
type oneloginVerify struct {
Status oneloginVerifyStatus `json:"status"`
}
func onelogin(db *database.Database, provider *settings.SecondaryProvider,
r *http.Request, usr *user.User, factor, passcode string) (
result bool, err error) {
if factor != Push && factor != Passcode {
err = &errortypes.UnknownError{
errors.New("secondary: OneLogin invalid factor"),
}
return
}
if factor == Passcode && passcode == "" {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin passcode empty"),
}
return
}
apiUrl := fmt.Sprintf(
"https://api.%s.onelogin.com",
provider.OneLoginRegion,
)
authParams := &oneloginAuthParams{
GrantType: "client_credentials",
}
authBody, err := json.Marshal(authParams)
if err != nil {
err = &errortypes.ParseError{
errors.New("secondary: OneLogin failed to parse auth params"),
}
return
}
reqUrl, _ := url.Parse(apiUrl + "/auth/oauth2/v2/token")
req, err := http.NewRequest(
"POST",
reqUrl.String(),
bytes.NewBuffer(authBody),
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin auth request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set(
"Authorization",
fmt.Sprintf(
"client_id:%s, client_secret:%s",
provider.OneLoginId,
provider.OneLoginSecret,
),
)
resp, err := oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin auth request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: OneLogin request error")
if err != nil {
return
}
auth := &oneloginAuth{}
err = json.NewDecoder(resp.Body).Decode(auth)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin auth parse failed"),
}
return
}
apiHeader := fmt.Sprintf(
"bearer:%s",
auth.AccessToken,
)
reqVals := url.Values{}
reqVals.Set("username", usr.Username)
reqVals.Set("fields", "id,username,email,status")
reqUrl, _ = url.Parse(apiUrl + "/api/1/users")
reqUrl.RawQuery = reqVals.Encode()
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin users request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin users request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: OneLogin request error")
if err != nil {
return
}
users := &oneloginUsers{}
err = json.NewDecoder(resp.Body).Decode(users)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin users parse failed"),
}
return
}
if users.Data == nil || len(users.Data) == 0 {
reqVals := url.Values{}
reqVals.Set("email", usr.Username)
reqVals.Set("fields", "id,username,email,status")
reqUrl, _ = url.Parse(apiUrl + "/api/1/users")
reqUrl.RawQuery = reqVals.Encode()
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin users request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin users request failed"),
}
return
}
defer resp.Body.Close()
users = &oneloginUsers{}
err = json.NewDecoder(resp.Body).Decode(users)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin users parse failed"),
}
return
}
}
shortUsername := ""
if (users.Data == nil || len(users.Data) == 0) &&
strings.Contains(usr.Username, "@") {
shortUsername = strings.SplitN(usr.Username, "@", 2)[0]
reqVals := url.Values{}
reqVals.Set("username", shortUsername)
reqVals.Set("fields", "id,username,email,status")
reqUrl, _ = url.Parse(apiUrl + "/api/1/users")
reqUrl.RawQuery = reqVals.Encode()
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin users request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin users request failed"),
}
return
}
defer resp.Body.Close()
users = &oneloginUsers{}
err = json.NewDecoder(resp.Body).Decode(users)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin users parse failed"),
}
return
}
}
if users.Data == nil || len(users.Data) == 0 {
err = &errortypes.NotFoundError{
errors.New("secondary: OneLogin user not found"),
}
return
}
if users.Data[0].Id == 0 {
err = &errortypes.NotFoundError{
errors.New("secondary: OneLogin unknown user ID"),
}
return
}
if usr.Username != users.Data[0].Username &&
usr.Username != users.Data[0].Email &&
(shortUsername != "" && shortUsername != users.Data[0].Username) {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin username mismatch"),
}
return
}
if users.Data[0].Status != 1 {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin user is not active"),
}
return
}
userId := users.Data[0].Id
reqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(
"/api/1/users/%d/otp_devices",
userId,
))
req, err = http.NewRequest(
"GET",
reqUrl.String(),
nil,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin devices request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Authorization", apiHeader)
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin devices request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: OneLogin request error")
if err != nil {
return
}
devices := &oneloginOtpDevices{}
err = json.NewDecoder(resp.Body).Decode(devices)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin users parse failed"),
}
return
}
if devices.Data.OtpDevices == nil || len(devices.Data.OtpDevices) == 0 {
err = &errortypes.NotFoundError{
errors.New("secondary: OneLogin user has no devices"),
}
return
}
deviceId := 0
needsTrigger := false
for _, device := range devices.Data.OtpDevices {
if device.AuthFactorName != "OneLogin Protect" {
continue
}
if device.Default {
deviceId = device.Id
needsTrigger = device.NeedsTrigger
break
} else if deviceId == 0 {
deviceId = device.Id
needsTrigger = device.NeedsTrigger
}
}
if deviceId == 0 {
err = &errortypes.NotFoundError{
errors.New("secondary: OneLogin user device type not found"),
}
return
}
stateToken := ""
if needsTrigger || factor == Push {
reqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(
"/api/1/users/%d/otp_devices/%d/trigger",
userId,
deviceId,
))
var activateBuffer *bytes.Buffer
if factor == Push {
activateParams := &oneloginActivateParams{
IpAddr: node.Self.GetRemoteAddr(r),
}
activateBody, e := json.Marshal(activateParams)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(
e,
"secondary: OneLogin failed to parse activate params",
),
}
return
}
activateBuffer = bytes.NewBuffer(activateBody)
}
req, err = http.NewRequest(
"POST",
reqUrl.String(),
activateBuffer,
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin activate request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", apiHeader)
req.Header.Set("User-Agent", r.UserAgent())
req.Header.Set("X-Forwarded-For", node.Self.GetRemoteAddr(r))
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(
err, "secondary: OneLogin activate request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequest(resp, "secondary: OneLogin request error")
if err != nil {
return
}
activate := &oneloginActivate{}
err = json.NewDecoder(resp.Body).Decode(activate)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin activate parse failed"),
}
return
}
if activate.Data == nil || len(activate.Data) == 0 {
err = &errortypes.UnknownError{
errors.New("secondary: OneLogin activate empty data"),
}
return
}
if activate.Data[0].Id != userId {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin activate user id mismatch"),
}
return
}
if activate.Data[0].DeviceId != deviceId {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin activate device id mismatch"),
}
return
}
if activate.Data[0].StateToken == "" {
err = &errortypes.AuthenticationError{
errors.New("secondary: OneLogin activate state token empty"),
}
return
}
stateToken = activate.Data[0].StateToken
}
verifyParams := &oneloginVerifyParams{
OtpToken: passcode,
StateToken: stateToken,
}
verifyBody, err := json.Marshal(verifyParams)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(
err, "secondary: OneLogin failed to parse verify params"),
}
return
}
start := time.Now()
for {
if time.Now().Sub(start) > 45*time.Second {
err = audit.New(
db,
r,
usr.Id,
audit.OneLoginDeny,
audit.Fields{
"one_login_factor": factor,
"one_login_error": "timeout",
},
)
if err != nil {
return
}
result = false
return
}
reqUrl, _ = url.Parse(apiUrl + fmt.Sprintf(
"/api/1/users/%d/otp_devices/%d/verify",
userId,
deviceId,
))
req, err = http.NewRequest(
"POST",
reqUrl.String(),
bytes.NewBuffer(verifyBody),
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin verify request failed"),
}
return
}
req.Header.Set("Accept", "application/json")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", apiHeader)
req.Header.Set("User-Agent", r.UserAgent())
req.Header.Set("X-Forwarded-For", node.Self.GetRemoteAddr(r))
resp, err = oneloginClient.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secondary: OneLogin verify request failed"),
}
return
}
defer resp.Body.Close()
err = utils.CheckRequestN(
resp, "secondary: OneLogin verify request failed",
[]int{200, 401},
)
if err != nil {
return
}
verify := &oneloginVerify{}
err = json.NewDecoder(resp.Body).Decode(verify)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secondary: OneLogin verify parse failed"),
}
return
}
if resp.StatusCode == 401 {
if strings.Contains(
verify.Status.Message, "Authentication pending") {
time.Sleep(500 * time.Millisecond)
continue
}
err = audit.New(
db,
r,
usr.Id,
audit.OneLoginDeny,
audit.Fields{
"one_login_factor": factor,
},
)
if err != nil {
return
}
result = false
return
}
if verify.Status.Type != "success" || verify.Status.Code != 200 ||
verify.Status.Error {
err = &errortypes.UnknownError{
errors.New("secondary: OneLogin verify request bad data"),
}
return
}
err = audit.New(
db,
r,
usr.Id,
audit.OneLoginApprove,
audit.Fields{
"one_login_factor": factor,
},
)
if err != nil {
return
}
result = true
return
}
return
}
================================================
FILE: secondary/secondary.go
================================================
package secondary
import (
"bytes"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/device"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type SecondaryData struct {
Token string `json:"token"`
Label string `json:"label"`
Push bool `json:"push"`
Phone bool `json:"phone"`
Passcode bool `json:"passcode"`
Sms bool `json:"sms"`
Device bool `json:"device"`
DeviceRegister bool `json:"device_register"`
}
type Secondary struct {
usr *user.User `bson:"-"`
provider *settings.SecondaryProvider `bson:"-"`
Id string `bson:"_id"`
ProviderId bson.ObjectID `bson:"provider_id,omitempty"`
UserId bson.ObjectID `bson:"user_id"`
Type string `bson:"type"`
Timestamp time.Time `bson:"timestamp"`
PushSent bool `bson:"push_sent"`
PhoneSent bool `bson:"phone_sent"`
SmsSent bool `bson:"sms_sent"`
Disabled bool `bson:"disabled"`
WanSession *webauthn.SessionData `bson:"wan_session"`
}
// TODO Disable secondary after login
func (s *Secondary) Push(db *database.Database, r *http.Request) (
errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
if s.PushSent {
err = &errortypes.AuthenticationError{
errors.New("secondary: Push already sent"),
}
return
}
s.PushSent = true
err = s.CommitFields(db, set.NewSet("push_sent"))
if err != nil {
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
if !provider.PushFactor {
err = &errortypes.AuthenticationError{
errors.New("secondary: Push factor not available"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
result := false
switch provider.Type {
case Duo:
result, err = duo(db, provider, r, usr, Push, "")
if err != nil {
return
}
break
case OneLogin:
result, err = onelogin(db, provider, r, usr, Push, "")
if err != nil {
return
}
break
case Okta:
result, err = okta(db, provider, r, usr, Push, "")
if err != nil {
return
}
break
default:
err = &errortypes.UnknownError{
errors.New("secondary: Unknown secondary provider type"),
}
return
}
if !result {
errData = &errortypes.ErrorData{
Error: "secondary_denied",
Message: "Secondary authentication was denied",
}
return
}
return
}
func (s *Secondary) Phone(db *database.Database, r *http.Request) (
errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
if s.PhoneSent {
err = &errortypes.AuthenticationError{
errors.New("secondary: Phone already sent"),
}
return
}
s.PhoneSent = true
err = s.CommitFields(db, set.NewSet("phone_sent"))
if err != nil {
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
if !provider.PhoneFactor {
err = &errortypes.AuthenticationError{
errors.New("secondary: Phone factor not available"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
result := false
switch provider.Type {
case Duo:
result, err = duo(db, provider, r, usr, Phone, "")
if err != nil {
return
}
break
default:
err = &errortypes.UnknownError{
errors.New("secondary: Unknown secondary provider type"),
}
return
}
if !result {
errData = &errortypes.ErrorData{
Error: "secondary_denied",
Message: "Secondary authentication was denied",
}
return
}
return
}
func (s *Secondary) Passcode(db *database.Database, r *http.Request,
passcode string) (errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
if !provider.PasscodeFactor {
err = &errortypes.AuthenticationError{
errors.New("secondary: Passcode factor not available"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
result := false
switch provider.Type {
case Duo:
result, err = duo(db, provider, r, usr, Passcode, passcode)
if err != nil {
return
}
break
case OneLogin:
result, err = onelogin(db, provider, r, usr, Passcode, passcode)
if err != nil {
return
}
break
case Okta:
result, err = okta(db, provider, r, usr, Passcode, passcode)
if err != nil {
return
}
break
default:
err = &errortypes.UnknownError{
errors.New("secondary: Unknown secondary provider type"),
}
return
}
if !result {
errData = &errortypes.ErrorData{
Error: "secondary_denied",
Message: "Secondary authentication was denied",
}
return
}
return
}
func (s *Secondary) Sms(db *database.Database, r *http.Request) (
errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
if s.SmsSent {
err = &errortypes.AuthenticationError{
errors.New("secondary: Sms already sent"),
}
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
if !provider.SmsFactor {
err = &errortypes.AuthenticationError{
errors.New("secondary: Sms factor not available"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
switch provider.Type {
case Duo:
_, err = duo(db, provider, r, usr, Sms, "")
if err != nil {
return
}
break
default:
err = &errortypes.UnknownError{
errors.New("secondary: Unknown secondary provider type"),
}
return
}
s.SmsSent = true
err = s.CommitFields(db, set.NewSet("sms_sent"))
if err != nil {
return
}
err = &IncompleteError{
errors.New("secondary: Secondary auth is incomplete"),
}
return
}
func (s *Secondary) DeviceRegisterRequest(db *database.Database,
origin string) (jsonResp interface{}, errData *errortypes.ErrorData,
err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary registration has already been completed",
}
return
}
if s.ProviderId != DeviceProvider {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device register not available"),
}
return
}
if s.WanSession != nil {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device registration already requested"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
web, err := node.Self.GetWebauthn(origin, true)
if err != nil {
return
}
options, sessionData, err := web.BeginRegistration(usr)
if err != nil {
err = utils.ParseWebauthnError(err)
return
}
s.WanSession = sessionData
err = s.CommitFields(db, set.NewSet("wan_session"))
if err != nil {
return
}
jsonResp = options
return
}
func (s *Secondary) DeviceRegisterResponse(db *database.Database,
origin string, body io.Reader, name string) (
devc *device.Device, errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary registration has already been completed",
}
return
}
if s.ProviderId != DeviceProvider {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device register not available"),
}
return
}
if s.WanSession == nil {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device registration not requested"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
data, err := protocol.ParseCredentialCreationResponseBody(body)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Webauthn parse error"),
}
return
}
web, err := node.Self.GetWebauthn(origin, true)
if err != nil {
return
}
credential, err := web.CreateCredential(usr, *s.WanSession, data)
if err != nil {
err = utils.ParseWebauthnError(err)
return
}
devc = device.New(usr.Id, device.WebAuthn, device.Secondary)
devc.User = usr.Id
devc.Name = name
devc.WanRpId = web.Config.RPID
devc.MarshalWebauthn(credential)
errData, err = devc.Validate(db)
if err != nil || errData != nil {
return
}
err = devc.Insert(db)
if err != nil {
return
}
return
}
func (s *Secondary) DeviceRequest(db *database.Database, origin string) (
jsonResp interface{}, errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
if s.ProviderId != DeviceProvider {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device sign not available"),
}
return
}
if s.WanSession != nil {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device sign already requested"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
web, err := node.Self.GetWebauthn(origin, false)
if err != nil {
return
}
_, hasU2f, err := usr.LoadWebAuthnDevices(db)
if err != nil {
return
}
loginOpts := []webauthn.LoginOption{
webauthn.WithUserVerification(protocol.VerificationPreferred),
}
if hasU2f {
loginOpts = append(
loginOpts,
webauthn.WithAssertionExtensions(
protocol.AuthenticationExtensions{
"appid": settings.Local.AppId,
},
),
)
}
options, sessionData, err := web.BeginLogin(usr, loginOpts...)
if err != nil {
err = utils.ParseWebauthnError(err)
return
}
s.WanSession = sessionData
err = s.CommitFields(db, set.NewSet("wan_session"))
if err != nil {
return
}
jsonResp = options
return
}
func (s *Secondary) DeviceRespond(db *database.Database, origin string,
body io.Reader) (errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication has already been completed",
}
return
}
if s.ProviderId != DeviceProvider {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device sign not available"),
}
return
}
if s.WanSession == nil {
err = &errortypes.AuthenticationError{
errors.New("secondary: Device sign not requested"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
return
}
data, err := protocol.ParseCredentialRequestResponseBody(body)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Webauthn parse error"),
}
return
}
web, err := node.Self.GetWebauthn(origin, false)
if err != nil {
return
}
devices, _, err := usr.LoadWebAuthnDevices(db)
if err != nil {
return
}
credential, err := web.ValidateLogin(
usr, *s.WanSession, data)
if err != nil {
err = utils.ParseWebauthnError(err)
logrus.WithFields(logrus.Fields{
"user_id": s.UserId.Hex(),
"error": err,
}).Error("secondary: Secondary authentication was denied")
errData = &errortypes.ErrorData{
Error: "secondary_denied",
Message: "Secondary authentication was denied",
}
return
}
for _, devc := range devices {
if devc.Type == device.U2f {
if !bytes.Equal(devc.U2fKeyHandle, credential.ID) {
continue
}
} else if devc.Type == device.WebAuthn {
if !bytes.Equal(devc.WanId, credential.ID) ||
!bytes.Equal(devc.WanPublicKey, credential.PublicKey) {
continue
}
} else {
continue
}
devc.LastActive = time.Now()
devc.MarshalWebauthn(credential)
err = devc.CommitFields(db, set.NewSet(
"last_active", "u2f_counter", "wan_authenticator"))
if err != nil {
return
}
return
}
errData = &errortypes.ErrorData{
Error: "secondary_denied",
Message: "Secondary authentication was denied",
}
return
}
func (s *Secondary) GetData() (data *SecondaryData, err error) {
if s.ProviderId == DeviceProvider {
label := ""
register := false
if strings.Contains(s.Type, "register") {
label = "Register Device"
register = true
} else {
label = "Device Authentication"
register = false
}
data = &SecondaryData{
Token: s.Id,
Label: label,
Push: false,
Phone: false,
Passcode: false,
Sms: false,
Device: !register,
DeviceRegister: register,
}
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
data = &SecondaryData{
Token: s.Id,
Label: provider.Label,
Push: provider.PushFactor,
Phone: provider.PhoneFactor,
Passcode: provider.PasscodeFactor || provider.SmsFactor,
Sms: provider.SmsFactor,
}
return
}
func (s *Secondary) GetQuery() (query string, err error) {
if s.ProviderId == DeviceProvider {
label := ""
factor := ""
if strings.Contains(s.Type, "register") {
label = "Register Device"
factor = "device_register"
} else {
label = "Device Authentication"
factor = "device"
}
query = fmt.Sprintf(
"secondary=%s&label=%s&factors=%s",
s.Id,
url.PathEscape(label),
factor,
)
return
}
provider, err := s.GetProvider()
if err != nil {
return
}
factors := []string{}
if provider.PushFactor {
factors = append(factors, "push")
}
if provider.PhoneFactor {
factors = append(factors, "phone")
}
if provider.PasscodeFactor || provider.SmsFactor {
factors = append(factors, "passcode")
}
if provider.SmsFactor {
factors = append(factors, "sms")
}
query = fmt.Sprintf(
"secondary=%s&label=%s&factors=%s",
s.Id,
url.PathEscape(provider.Label),
strings.Join(factors, ","),
)
return
}
func (s *Secondary) Complete(db *database.Database) (
errData *errortypes.ErrorData, err error) {
if s.Disabled {
errData = &errortypes.ErrorData{
Error: "secondary_disabled",
Message: "Secondary authentication is already completed",
}
return
}
s.Disabled = true
coll := db.SecondaryTokens()
resp, err := coll.UpdateOne(db, &bson.M{
"_id": s.Id,
"disabled": false,
}, &bson.M{
"$set": &bson.M{
"disabled": true,
},
})
if err != nil {
err = database.ParseError(err)
return
}
if resp.ModifiedCount == 0 {
errData = &errortypes.ErrorData{
Error: "secondary_update_disabled",
Message: "Secondary authentication update is already completed",
}
return
}
return
}
func (s *Secondary) Handle(db *database.Database, r *http.Request,
factor, passcode string) (errData *errortypes.ErrorData, err error) {
switch factor {
case Push:
errData, err = s.Push(db, r)
break
case Phone:
errData, err = s.Phone(db, r)
break
case Passcode:
errData, err = s.Passcode(db, r, passcode)
break
case Sms:
errData, err = s.Sms(db, r)
break
default:
err = &errortypes.UnknownError{
errors.New("secondary: Unknown secondary factor"),
}
}
if err == nil && errData == nil && factor != Sms {
errData, err = s.Complete(db)
if err != nil || errData != nil {
return
}
}
return
}
func (s *Secondary) GetUser(db *database.Database) (
usr *user.User, err error) {
if s.usr != nil {
usr = s.usr
return
}
usr, err = user.Get(db, s.UserId)
if err != nil {
return
}
s.usr = usr
return
}
func (s *Secondary) GetProvider() (provider *settings.SecondaryProvider,
err error) {
provider = settings.Auth.GetSecondaryProvider(s.ProviderId)
if provider == nil {
err = &errortypes.NotFoundError{
errors.New("secondary: Secondary provider not found"),
}
return
}
return
}
func (s *Secondary) Commit(db *database.Database) (err error) {
coll := db.SecondaryTokens()
err = coll.Commit(s.Id, s)
if err != nil {
return
}
return
}
func (s *Secondary) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.SecondaryTokens()
err = coll.CommitFields(s.Id, s, fields)
if err != nil {
return
}
return
}
func (s *Secondary) Insert(db *database.Database) (err error) {
coll := db.SecondaryTokens()
_, err = coll.InsertOne(db, s)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: secondary/utils.go
================================================
package secondary
import (
"math/rand"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
func New(db *database.Database, userId bson.ObjectID, typ string,
proivderId bson.ObjectID) (secd *Secondary, err error) {
token, err := utils.RandStr(64)
if err != nil {
return
}
secd = &Secondary{
Id: token,
UserId: userId,
Type: typ,
ProviderId: proivderId,
Timestamp: time.Now(),
}
err = secd.Insert(db)
if err != nil {
return
}
return
}
func Get(db *database.Database, token string, typ string) (
secd *Secondary, err error) {
coll := db.SecondaryTokens()
secd = &Secondary{}
timestamp := time.Now().Add(
-time.Duration(settings.Auth.SecondaryExpire) * time.Second)
time.Sleep(time.Duration(rand.Intn(10)) * time.Millisecond)
err = coll.FindOne(db, &bson.M{
"_id": token,
"type": typ,
"timestamp": &bson.M{
"$gte": timestamp,
},
}).Decode(secd)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, token string) (err error) {
coll := db.SecondaryTokens()
_, err = coll.DeleteMany(db, &bson.M{
"_id": token,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: secret/constants.go
================================================
package secret
import "github.com/pritunl/mongo-go-driver/v2/bson"
const (
AWS = "aws"
Cloudflare = "cloudflare"
OracleCloud = "oracle_cloud"
GoogleCloud = "google_cloud"
Json = "json"
)
var (
Global = bson.NilObjectID
)
================================================
FILE: secret/oracle.go
================================================
package secret
import (
"crypto/rsa"
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/core"
"github.com/oracle/oci-go-sdk/v65/dns"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type OracleProvider struct {
privateKey *rsa.PrivateKey
tenancy string
user string
fingerprint string
region string
compartment string
dnsClient *dns.DnsClient
computeClient *core.ComputeClient
}
func (p *OracleProvider) AuthType() (common.AuthConfig, error) {
return common.AuthConfig{
AuthType: common.UserPrincipal,
IsFromConfigFile: false,
OboToken: nil,
}, nil
}
func (p *OracleProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
return p.privateKey, nil
}
func (p *OracleProvider) KeyID() (string, error) {
return fmt.Sprintf("%s/%s/%s", p.tenancy, p.user, p.fingerprint), nil
}
func (p *OracleProvider) TenancyOCID() (string, error) {
return p.tenancy, nil
}
func (p *OracleProvider) UserOCID() (string, error) {
return p.user, nil
}
func (p *OracleProvider) KeyFingerprint() (string, error) {
return p.fingerprint, nil
}
func (p *OracleProvider) Region() (string, error) {
return p.region, nil
}
func (p *OracleProvider) CompartmentOCID() (string, error) {
return p.compartment, nil
}
func (p *OracleProvider) GetDnsClient() (
dnsClient *dns.DnsClient, err error) {
if p.dnsClient != nil {
dnsClient = p.dnsClient
return
}
client, err := dns.NewDnsClientWithConfigurationProvider(p)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "secret: Failed to create oracle client"),
}
return
}
p.dnsClient = &client
dnsClient = p.dnsClient
return
}
func NewOracleProvider(secr *Secret) (prov *OracleProvider, err error) {
privateKey, fingerprint, err := loadPrivateKey(secr)
if err != nil {
return
}
prov = &OracleProvider{
privateKey: privateKey,
tenancy: secr.Key,
user: secr.Value,
fingerprint: fingerprint,
region: secr.Region,
compartment: secr.Key,
}
return
}
================================================
FILE: secret/secret.go
================================================
package secret
import (
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Secret struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Type string `bson:"type" json:"type"`
Key string `bson:"key" json:"key"`
Value string `bson:"value" json:"value"`
Region string `bson:"region" json:"region"`
PublicKey string `bson:"public_key" json:"public_key"`
Data string `bson:"data" json:"data"`
PrivateKey string `bson:"private_key" json:"-"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Type string `bson:"type" json:"type"`
}
func (c *Secret) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
c.Name = utils.FilterName(c.Name)
switch c.Type {
case AWS, "":
c.Type = AWS
if c.Region == "" {
c.Region = "us-east-1"
}
break
case Cloudflare:
c.Value = ""
c.Region = ""
break
case OracleCloud:
break
case GoogleCloud:
c.Value = ""
c.Region = ""
break
case Json:
c.Key = ""
c.Value = ""
c.Region = ""
if !JsonValid(c.Data) {
errData = &errortypes.ErrorData{
Error: "invalid_secret_json",
Message: "Secret json data invalid",
}
return
}
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_secret_type",
Message: "Secret type invalid",
}
return
}
if c.PrivateKey == "" {
privKey, pubKey, e := utils.GenerateRsaKey()
if e != nil {
err = e
return
}
c.PublicKey = strings.TrimSpace(string(pubKey))
c.PrivateKey = strings.TrimSpace(string(privKey))
}
return
}
func (c *Secret) GetOracleProvider() (prov *OracleProvider, err error) {
prov, err = NewOracleProvider(c)
if err != nil {
return
}
return
}
func (c *Secret) Commit(db *database.Database) (err error) {
coll := db.Secrets()
err = coll.Commit(c.Id, c)
if err != nil {
return
}
return
}
func (c *Secret) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Secrets()
err = coll.CommitFields(c.Id, c, fields)
if err != nil {
return
}
return
}
func (c *Secret) Insert(db *database.Database) (err error) {
coll := db.Secrets()
if !c.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("secret: Secret already exists"),
}
return
}
_, err = coll.InsertOne(db, c)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: secret/utils.go
================================================
package secret
import (
"bytes"
"crypto/md5"
"crypto/rsa"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
func JsonValid(data string) bool {
var dataMap map[string]any
err := json.Unmarshal([]byte(data), &dataMap)
if err != nil {
return false
}
for _, value := range dataMap {
switch value.(type) {
case string, bool, float64, int, int64, nil:
continue
default:
return false
}
}
return true
}
func Get(db *database.Database, secrId bson.ObjectID) (
secr *Secret, err error) {
coll := db.Secrets()
secr = &Secret{}
err = coll.FindOneId(secrId, secr)
if err != nil {
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (secr *Secret, err error) {
coll := db.Secrets()
secr = &Secret{}
err = coll.FindOne(db, query).Decode(secr)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOrg(db *database.Database, orgId, secrId bson.ObjectID) (
secr *Secret, err error) {
coll := db.Secrets()
secr = &Secret{}
err = coll.FindOne(db, &bson.M{
"_id": secrId,
"organization": orgId,
}).Decode(secr)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
secrs []*Secret, err error) {
coll := db.Secrets()
secrs = []*Secret{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
secr := &Secret{}
err = cursor.Decode(secr)
if err != nil {
return
}
secrs = append(secrs, secr)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllOrg(db *database.Database, orgId bson.ObjectID) (
secrs []*Secret, err error) {
coll := db.Secrets()
secrs = []*Secret{}
cursor, err := coll.Find(db, &bson.M{
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
secr := &Secret{}
err = cursor.Decode(secr)
if err != nil {
return
}
secrs = append(secrs, secr)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNames(db *database.Database, query *bson.M) (
secrs []*database.Named, err error) {
coll := db.Certificates()
secrs = []*database.Named{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetProjection(&bson.D{
{"name", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
secr := &database.Named{}
err = cursor.Decode(secr)
if err != nil {
err = database.ParseError(err)
return
}
secrs = append(secrs, secr)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (secrs []*Secret, count int64, err error) {
coll := db.Secrets()
secrs = []*Secret{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
secr := &Secret{}
err = cursor.Decode(secr)
if err != nil {
err = database.ParseError(err)
return
}
secrs = append(secrs, secr)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func ExistsOrg(db *database.Database, orgId, secrId bson.ObjectID) (
exists bool, err error) {
coll := db.Secrets()
n, err := coll.CountDocuments(
db,
&bson.M{
"_id": secrId,
"organization": orgId,
},
)
if err != nil {
return
}
if n > 0 {
exists = true
}
return
}
func Remove(db *database.Database, secrId bson.ObjectID) (err error) {
coll := db.Secrets()
_, err = coll.DeleteMany(db, &bson.M{
"_id": secrId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveOrg(db *database.Database, orgId, secrId bson.ObjectID) (
err error) {
coll := db.Secrets()
_, err = coll.DeleteOne(db, &bson.M{
"_id": secrId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, secrIds []bson.ObjectID) (
err error) {
coll := db.Secrets()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": secrIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
secrIds []bson.ObjectID) (err error) {
coll := db.Secrets()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": secrIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func loadPrivateKey(secr *Secret) (
key *rsa.PrivateKey, fingerprint string, err error) {
block, _ := pem.Decode([]byte(secr.PrivateKey))
if block == nil {
err = &errortypes.ParseError{
errors.New("secret: Failed to decode private key"),
}
return
}
if block.Type != "RSA PRIVATE KEY" {
err = &errortypes.ParseError{
errors.New("secret: Invalid private key type"),
}
return
}
key, err = x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secret: Failed to parse rsa key"),
}
return
}
pubKey, err := x509.MarshalPKIXPublicKey(key.Public())
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "secret: Failed to marshal public key"),
}
return
}
keyHash := md5.New()
keyHash.Write(pubKey)
fingerprint = fmt.Sprintf("%x", keyHash.Sum(nil))
fingerprintBuf := bytes.Buffer{}
for i, run := range fingerprint {
fingerprintBuf.WriteRune(run)
if i%2 == 1 && i != len(fingerprint)-1 {
fingerprintBuf.WriteRune(':')
}
}
fingerprint = fingerprintBuf.String()
return
}
================================================
FILE: session/constants.go
================================================
package session
const (
Admin = "admin"
User = "user"
)
================================================
FILE: session/session.go
================================================
// Stores sessions in cookies.
package session
import (
"crypto/hmac"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/rokey"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/useragent"
"github.com/pritunl/pritunl-cloud/utils"
)
type Session struct {
Id string `bson:"_id" json:"id"`
Type string `bson:"type" json:"type"`
User bson.ObjectID `bson:"user" json:"user"`
Rokey bson.ObjectID `bson:"rokey" json:"-"`
Secret string `bson:"secret" json:"-"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
LastActive time.Time `bson:"last_active" json:"last_active"`
Removed bool `bson:"removed" json:"removed"`
Agent *useragent.Agent `bson:"agent" json:"agent"`
user *user.User `bson:"-" json:"-"`
}
func (s *Session) CheckSignature(db *database.Database, inSig string) (
valid bool, err error) {
if s.Rokey.IsZero() || s.Secret == "" {
return
}
rkey, err := rokey.GetId(db, s.Type, s.Rokey)
if err != nil {
return
}
if rkey == nil {
return
}
if rkey.Secret == "" {
err = &errortypes.ReadError{
errors.Wrap(err, "session: Empty secret"),
}
return
}
hash := hmac.New(sha512.New, []byte(rkey.Secret))
hash.Write([]byte(s.Secret))
outSig := base64.RawStdEncoding.EncodeToString(hash.Sum(nil))
if subtle.ConstantTimeCompare([]byte(inSig), []byte(outSig)) == 1 {
valid = true
}
return
}
func (s *Session) GenerateSignature(db *database.Database) (
sig string, err error) {
rkey, err := rokey.Get(db, s.Type)
if err != nil {
return
}
s.Rokey = rkey.Id
s.Secret, err = utils.RandStr(64)
if err != nil {
return
}
if rkey.Secret == "" {
err = &errortypes.ReadError{
errors.Wrap(err, "session: Empty secret"),
}
return
}
hash := hmac.New(sha512.New, []byte(rkey.Secret))
hash.Write([]byte(s.Secret))
sig = base64.RawStdEncoding.EncodeToString(hash.Sum(nil))
return
}
func (s *Session) Active() bool {
if s.Removed {
return false
}
expire := GetExpire(s.Type)
maxDuration := GetMaxDuration(s.Type)
if expire != 0 {
if time.Since(s.LastActive) > expire {
return false
}
}
if maxDuration != 0 {
if time.Since(s.Timestamp) > maxDuration {
return false
}
}
return true
}
func (s *Session) Update(db *database.Database) (err error) {
coll := db.Sessions()
err = coll.FindOneId(s.Id, s)
if err != nil {
return
}
return
}
func (s *Session) Remove(db *database.Database) (err error) {
err = Remove(db, s.Id)
if err != nil {
return
}
return
}
func (s *Session) GetUser(db *database.Database) (usr *user.User, err error) {
if s.user != nil || db == nil {
usr = s.user
return
}
usr, err = user.GetUpdate(db, s.User)
if err != nil {
return
}
s.user = usr
return
}
================================================
FILE: session/utils.go
================================================
package session
import (
"net/http"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/useragent"
"github.com/pritunl/pritunl-cloud/utils"
)
func GetExpire(typ string) time.Duration {
switch typ {
case User:
return time.Duration(settings.Auth.UserExpire) * time.Minute
default:
return time.Duration(settings.Auth.AdminExpire) * time.Minute
}
}
func GetMaxDuration(typ string) time.Duration {
switch typ {
case User:
return time.Duration(settings.Auth.UserMaxDuration) * time.Minute
default:
return time.Duration(settings.Auth.AdminMaxDuration) * time.Minute
}
}
func Get(db *database.Database, sessId string) (
sess *Session, err error) {
coll := db.Sessions()
sess = &Session{}
err = coll.FindOneId(sessId, sess)
if err != nil {
return
}
return
}
func GetUpdate(db *database.Database, sessId string, r *http.Request,
typ, sig string) (sess *Session, err error) {
query := bson.M{
"_id": sessId,
"removed": &bson.M{
"$ne": true,
},
}
expire := GetExpire(typ)
maxDuration := GetMaxDuration(typ)
if expire != 0 {
query["last_active"] = &bson.M{
"$gte": time.Now().Add(-expire),
}
}
if maxDuration != 0 {
query["timestamp"] = &bson.M{
"$gte": time.Now().Add(-maxDuration),
}
}
coll := db.Sessions()
sess = &Session{}
timestamp := time.Now()
err = coll.FindOneAndUpdate(
db,
query,
&bson.M{
"$set": &bson.M{
"last_active": timestamp,
},
},
).Decode(sess)
if err != nil {
err = database.ParseError(err)
return
}
sess.LastActive = timestamp
valid, err := sess.CheckSignature(db, sig)
if err != nil {
return
}
if !valid {
sess = nil
return
}
agnt, err := useragent.Parse(db, r)
if err != nil {
return
}
if agnt != nil && (sess.Agent == nil || sess.Agent.Diff(agnt)) {
sess.Agent = agnt
err = coll.UpdateId(sess.Id, &bson.M{
"$set": &bson.M{
"agent": agnt,
},
})
if err != nil {
err = database.ParseError(err)
return
}
}
return
}
func GetAll(db *database.Database, userId bson.ObjectID,
includeRemoved bool) (sessions []*Session, err error) {
coll := db.Sessions()
sessions = []*Session{}
cursor, err := coll.Find(db, &bson.M{
"user": userId,
})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
sess := &Session{}
err = cursor.Decode(sess)
if err != nil {
err = database.ParseError(err)
return
}
if !sess.Active() {
if !includeRemoved {
continue
}
sess.Removed = true
}
sessions = append(sessions, sess)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func New(db *database.Database, r *http.Request, userId bson.ObjectID,
typ string) (sess *Session, sig string, err error) {
id, err := utils.RandStr(32)
if err != nil {
return
}
agnt, err := useragent.Parse(db, r)
if err != nil {
return
}
coll := db.Sessions()
sess = &Session{
Id: id,
Type: typ,
User: userId,
Timestamp: time.Now(),
LastActive: time.Now(),
Agent: agnt,
}
sig, err = sess.GenerateSignature(db)
if err != nil {
return
}
_, err = coll.InsertOne(db, sess)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, id string) (err error) {
coll := db.Sessions()
err = coll.UpdateId(id, &bson.M{
"$set": &bson.M{
"removed": true,
},
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveAll(db *database.Database, userId bson.ObjectID) (err error) {
coll := db.Sessions()
_, err = coll.UpdateMany(db, &bson.M{
"user": userId,
}, &bson.M{
"$set": &bson.M{
"removed": true,
},
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
================================================
FILE: settings/acme.go
================================================
package settings
var Acme *acme
type acme struct {
Id string `bson:"_id"`
Url string `bson:"url" default:"https://acme-v01.api.letsencrypt.org"`
DnsMaxConcurrent int `bson:"dns_max_concurrent" default:"10"`
DnsRetryRate int `bson:"dns_retry_rate" default:"3"`
DnsTimeout int `bson:"dns_timeout" default:"45"`
DnsDelay int `bson:"dns_delay" default:"15"`
DnsAwsTtl int `bson:"dns_aws_ttl" default:"10"`
DnsCloudflareTtl int `bson:"dns_cloudflare_ttl" default:"60"`
DnsOracleCloudTtl int `bson:"dns_oracle_cloud_ttl" default:"10"`
DnsGoogleCloudTtl int `bson:"dns_google_cloud_ttl" default:"10"`
}
func newAcme() interface{} {
return &acme{
Id: "acme",
}
}
func updateAcme(data interface{}) {
Acme = data.(*acme)
}
func init() {
register("acme", newAcme, updateAcme)
}
================================================
FILE: settings/auth.go
================================================
package settings
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
var Auth *auth
const (
SetOnInsert = "set_on_insert"
Merge = "merge"
Overwrite = "overwrite"
Azure = "azure"
AuthZero = "authzero"
Google = "google"
OneLogin = "onelogin"
Okta = "okta"
JumpCloud = "jumpcloud"
Duo = "duo"
OneLogin2 = "one_login"
)
type Provider struct {
Id bson.ObjectID `bson:"id" json:"id"`
Type string `bson:"type" json:"type"`
Label string `bson:"label" json:"label"`
DefaultRoles []string `bson:"default_roles" json:"default_roles"`
AutoCreate bool `bson:"auto_create" json:"auto_create"`
RoleManagement string `bson:"role_management" json:"role_management"`
Region string `bson:"region" json:"region"` // azure
Tenant string `bson:"tenant" json:"tenant"` // azure
ClientId string `bson:"client_id" json:"client_id"` // azure + authzero
ClientSecret string `bson:"client_secret" json:"client_secret"` // azure + authzero
Domain string `bson:"domain" json:"domain"` // google + authzero
GoogleKey string `bson:"google_key" json:"google_key"` // google
GoogleEmail string `bson:"google_email" json:"google_email"` // google
JumpCloudAppId string `bson:"jumpcloud_app_id" json:"jumpcloud_app_id"` // jumpcloud
JumpCloudSecret string `bson:"jumpcloud_secret" json:"jumpcloud_secret"` // jumpcloud
IssuerUrl string `bson:"issuer_url" json:"issuer_url"` // saml
SamlUrl string `bson:"saml_url" json:"saml_url"` // saml
SamlCert string `bson:"saml_cert" json:"saml_cert"` // saml
}
func (p *Provider) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
if p.Id.IsZero() {
p.Id = bson.NewObjectID()
}
p.Label = utils.FilterStr(p.Label, 32)
switch p.Type {
case AuthZero:
p.Region = ""
p.Tenant = ""
p.GoogleKey = ""
p.GoogleEmail = ""
p.JumpCloudAppId = ""
p.JumpCloudSecret = ""
p.IssuerUrl = ""
p.SamlUrl = ""
p.SamlCert = ""
break
case Azure:
if p.Region == "" {
p.Region = "global2"
}
p.Domain = ""
p.GoogleKey = ""
p.GoogleEmail = ""
p.JumpCloudAppId = ""
p.JumpCloudSecret = ""
p.IssuerUrl = ""
p.SamlUrl = ""
p.SamlCert = ""
break
case Google:
p.Region = ""
p.Tenant = ""
p.ClientId = ""
p.ClientSecret = ""
p.JumpCloudAppId = ""
p.JumpCloudSecret = ""
p.IssuerUrl = ""
p.SamlUrl = ""
p.SamlCert = ""
break
case OneLogin:
p.Region = ""
p.Tenant = ""
p.ClientId = ""
p.ClientSecret = ""
p.Domain = ""
p.GoogleKey = ""
p.GoogleEmail = ""
p.JumpCloudAppId = ""
p.JumpCloudSecret = ""
break
case Okta:
p.Region = ""
p.Tenant = ""
p.ClientId = ""
p.ClientSecret = ""
p.Domain = ""
p.GoogleKey = ""
p.GoogleEmail = ""
p.JumpCloudAppId = ""
p.JumpCloudSecret = ""
break
case JumpCloud:
p.Region = ""
p.Tenant = ""
p.ClientId = ""
p.ClientSecret = ""
p.Domain = ""
p.GoogleKey = ""
p.GoogleEmail = ""
break
default:
errData = &errortypes.ErrorData{
Error: "unknown_provider_type",
Message: "Unknown authentication provider type",
}
return
}
switch p.RoleManagement {
case SetOnInsert, "":
break
case Merge:
break
case Overwrite:
break
default:
errData = &errortypes.ErrorData{
Error: "unknown_role_management",
Message: "Unknown role management mode",
}
return
}
return
}
type SecondaryProvider struct {
Id bson.ObjectID `bson:"id" json:"id"`
Type string `bson:"type" json:"type"`
Name string `bson:"name" json:"name"`
Label string `bson:"label" json:"label"`
DuoHostname string `bson:"duo_hostname" json:"duo_hostname"` // duo
DuoKey string `bson:"duo_key" json:"duo_key"` // duo
DuoSecret string `bson:"duo_secret" json:"duo_secret"` // duo
OneLoginRegion string `bson:"one_login_region" json:"one_login_region"` // onelogin
OneLoginId string `bson:"one_login_id" json:"one_login_id"` // onelogin
OneLoginSecret string `bson:"one_login_secret" json:"one_login_secret"` // onelogin
OktaDomain string `bson:"okta_domain" json:"okta_domain"` // okta
OktaToken string `bson:"okta_token" json:"okta_token"` // okta
PushFactor bool `bson:"push_factor" json:"push_factor"` // duo + onelogin + okta
PhoneFactor bool `bson:"phone_factor" json:"phone_factor"` // duo + onelogin + okta
PasscodeFactor bool `bson:"passcode_factor" json:"passcode_factor"` // duo + onelogin + okta
SmsFactor bool `bson:"sms_factor" json:"sms_factor"` // duo + onelogin + okta
}
func (p *SecondaryProvider) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
if p.Id.IsZero() {
p.Id = bson.NewObjectID()
}
p.Name = utils.FilterStr(p.Name, 32)
p.Label = utils.FilterStr(p.Label, 32)
switch p.Type {
case Duo:
p.OneLoginRegion = ""
p.OneLoginId = ""
p.OneLoginSecret = ""
p.OktaDomain = ""
p.OktaToken = ""
break
case OneLogin2:
p.DuoHostname = ""
p.DuoKey = ""
p.DuoSecret = ""
p.OktaDomain = ""
p.OktaToken = ""
if p.OneLoginRegion == "" {
p.OneLoginRegion = "us"
}
break
case Okta:
p.DuoHostname = ""
p.DuoKey = ""
p.DuoSecret = ""
p.OneLoginRegion = ""
p.OneLoginId = ""
p.OneLoginSecret = ""
break
default:
errData = &errortypes.ErrorData{
Error: "unknown_secondary_provider_type",
Message: "Unknown secondary authentication provider type",
}
return
}
return
}
type auth struct {
Id string `bson:"_id"`
Server string `bson:"server" default:"https://auth.pritunl.com"`
Sync int `bson:"sync" json:"sync" default:"1800"`
CookieAge int `bson:"cookie_age" json:"cookie_age" default:"63072000"`
Providers []*Provider `bson:"providers"`
SecondaryProviders []*SecondaryProvider `bson:"secondary_providers"`
FastLogin bool `bson:"fast_login" json:"fast_login"`
ForceFastUserLogin bool `bson:"force_fast_user_login" json:"force_fast_user_login"`
Window int `bson:"window" json:"window" default:"60"`
SecondaryExpire int `bson:"secondary_expire" json:"secondary_expire" default:"90"`
AdminExpire int `bson:"admin_expire" json:"admin_expire" default:"1440"`
AdminMaxDuration int `bson:"admin_max_duration" json:"admin_max_duration" default:"4320"`
UserExpire int `bson:"user_expire" json:"user_expire" default:"1440"`
UserMaxDuration int `bson:"user_max_duration" json:"user_max_duration" default:"4320"`
}
func (a *auth) GetProvider(id bson.ObjectID) *Provider {
for _, provider := range a.Providers {
if provider.Id == id {
return provider
}
}
return nil
}
func (a *auth) GetSecondaryProvider(id bson.ObjectID) *SecondaryProvider {
for _, provider := range a.SecondaryProviders {
if provider.Id == id {
return provider
}
}
return nil
}
func newAuth() interface{} {
return &auth{
Id: "auth",
Providers: []*Provider{},
SecondaryProviders: []*SecondaryProvider{},
}
}
func updateAuth(data interface{}) {
Auth = data.(*auth)
}
func init() {
register("auth", newAuth, updateAuth)
}
================================================
FILE: settings/hypervisor.go
================================================
package settings
var Hypervisor *hypervisor
type hypervisor struct {
Id string `bson:"_id"`
SystemdPath string `bson:"systemd_path" default:"/etc/systemd/system"`
LibPath string `bson:"lib_path" default:"/var/lib/pritunl-cloud"`
RunPath string `bson:"run_path" default:"/var/run/pritunl-cloud"`
AgentHostPath string `bson:"agent_host_path" default:"/usr/bin/pritunl-cloud-agent"`
AgentBsdHostPath string `bson:"agent_bsd_host_path" default:"/usr/bin/pritunl-cloud-agent-bsd"`
AgentGuestPath string `bson:"agent_guest_path" default:"/usr/bin/pci"`
InitGuestPath string `bson:"init_guest_path" default:"/etc/pritunl-cloud-init"`
HugepagesPath string `bson:"hugepages_path" default:"/dev/hugepages/pritunl"`
LockCloudPass bool `bson:"lock_cloud_pass"`
DesktopEnv string `bson:"desktop_env" default:"gnome"`
OvmfCodePath string `bson:"ovmf_code_path"`
OvmfVarsPath string `bson:"ovmf_vars_path"`
OvmfSecureCodePath string `bson:"ovmf_secure_code_path"`
OvmfSecureVarsPath string `bson:"ovmf_secure_vars_path"`
NbdPath string `bson:"nbd_path" default:"/dev/nbd6"`
DiskAio string `bson:"disk_aio"`
NoSandbox bool `bson:"no_sandbox"`
GlHostMem int `bson:"gl_host_mem" default:"2048"`
BridgeIfaceName string `bson:"bridge_iface_name" default:"br0"`
ImdsIfaceName string `bson:"imds_iface_name" default:"imds0"`
NormalMtu int `bson:"normal_mtu" default:"1500"`
JumboMtu int `bson:"jumbo_mtu" default:"9000"`
DiskQueuesMin int `bson:"disk_queues_min" default:"1"`
DiskQueuesMax int `bson:"disk_queues_max" default:"4"`
NetworkQueuesMin int `bson:"network_queues_min" default:"1"`
NetworkQueuesMax int `bson:"network_queues_max" default:"8"`
CloudInitNetVer int `bson:"cloud_init_net_ver" default:"1"`
HostNetwork string `bson:"host_network" default:"198.18.84.0/22"`
HostNetworkName string `bson:"host_network_name" default:"pritunlhost0"`
VirtRng bool `bson:"virt_rng"`
VlanRanges string `bson:"vlan_ranges" default:"1001-3999"`
VxlanId int `bson:"vxlan_id" default:"9417"`
VxlanDestPort int `bson:"vxlan_dest_port" default:"4789"`
IpTimeout int `bson:"ip_timeout" default:"30"`
IpTimeout6 int `bson:"ip_timeout6" default:"15"`
ActionRate int `bson:"action_rate" default:"3"`
NodePortNetwork string `bson:"node_port_network" default:"198.19.96.0/23"`
NodePortRanges string `bson:"node_port_ranges" default:"30000-32767"`
NodePortNetworkName string `bson:"node_port_network_name" default:"pritunlport0"`
AddressRefreshTtl int `bson:"address_refresh_ttl" default:"1800"`
StartTimeout int `bson:"start_timeout" default:"45"`
StopTimeout int `bson:"stop_timeout" default:"180"`
RefreshRate int `bson:"refresh_rate" default:"90"`
SplashTime int `bson:"splash_time" default:"60"`
DhcpRenewTtl int `bson:"dhcp_renew_ttl" default:"60"`
NoIpv6PingInit bool `bson:"no_ipv6_ping_init"`
Ipv6PingHost string `bson:"ipv6_ping_host" default:"2001:4860:4860::8888"`
ImdsAddress string `bson:"imds_address" default:"169.254.169.254/32"`
ImdsPort int `bson:"imds_port" default:"80"`
ImdsSyncLogTimeout int `bson:"imds_sync_log_timeout" default:"20"`
ImdsSyncRestartTimeout int `bson:"imds_sync_log_timeout" default:"30"`
InfoTtl int `bson:"info_ttl" default:"10"`
NoGuiFullscreen bool `bson:"no_gui_fullscreen"`
UsbHsPorts int `bson:"usb_hs_ports" default:"4"`
UsbSsPorts int `bson:"usb_ss_ports" default:"4"`
NoVirtioHid bool `bson:"no_virtio_hid"`
JournalDisplayLimit int64 `bson:"journal_display_limit" default:"3000"`
DhcpLifetime int `bson:"dhcp_lifetime" default:"3600"`
NdpRaInterval int `bson:"ndp_ra_interval" default:"6"`
DnsServerPrimary string `bson:"dns_server_primary" default:"8.8.8.8"`
DnsServerSecondary string `bson:"dns_server_secondary" default:"8.8.4.4"`
DnsServerPrimary6 string `bson:"dns_server_primary6" default:"2001:4860:4860::8888"`
DnsServerSecondary6 string `bson:"dns_server_secondary6" default:"2001:4860:4860::8844"`
NodePortMaxAttempts int `bson:"node_port_max_attempts" default:"10000"`
MaxDeploymentFailures int `bson:"max_deployment_failures" default:"3"`
}
func newHypervisor() interface{} {
return &hypervisor{
Id: "hypervisor",
}
}
func updateHypervisor(data interface{}) {
Hypervisor = data.(*hypervisor)
}
func init() {
register("hypervisor", newHypervisor, updateHypervisor)
}
================================================
FILE: settings/local.go
================================================
package settings
var Local *local
type local struct {
AppId string
Facets []string
NoLocalAuth bool
DisableWeb bool
DisableMsg string
}
func init() {
Local = &local{}
}
================================================
FILE: settings/registry.go
================================================
package settings
var (
registry = map[string]*group{}
)
type newFunc func() interface{}
type updateFunc func(interface{})
type group struct {
New newFunc
Update updateFunc
}
func register(name string, new newFunc, update updateFunc) {
grp := &group{
New: new,
Update: update,
}
registry[name] = grp
}
================================================
FILE: settings/router.go
================================================
package settings
var Router *router
type router struct {
Id string `bson:"_id"`
ReadTimeout int `bson:"read_timeout" default:"300"`
ReadHeaderTimeout int `bson:"read_header_timeout" default:"60"`
WriteTimeout int `bson:"write_timeout" default:"300"`
IdleTimeout int `bson:"idle_timeout" default:"60"`
DialTimeout int `bson:"dial_timeout" default:"60"`
DialKeepAlive int `bson:"dial_keep_alive" default:"60"`
MaxIdleConns int `bson:"max_idle_conns" default:"1000"`
MaxIdleConnsPerHost int `bson:"max_idle_conns_per_host" default:"100"`
IdleConnTimeout int `bson:"idle_conn_timeout" default:"90"`
HandshakeTimeout int `bson:"handshake_timeout" default:"10"`
ContinueTimeout int `bson:"continue_timeout" default:"10"`
MaxHeaderBytes int `bson:"max_header_bytes" default:"4194304"`
ForceRedirectSystemd bool `bson:"force_redirect_systemd"`
SkipVerify bool `bson:"skip_verify"`
ProxyResolverRefresh int `bson:"proxy_resolver_refresh" default:"30"`
ProxyResolverTtl int `bson:"proxy_resolver_ttl" default:"30"`
}
func newRouter() interface{} {
return &router{
Id: "router",
}
}
func updateRouter(data interface{}) {
Router = data.(*router)
}
func init() {
register("router", newRouter, updateRouter)
}
================================================
FILE: settings/settings.go
================================================
package settings
import (
"reflect"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
func Commit(db *database.Database, group interface{}, fields set.Set) (
err error) {
coll := db.Settings()
selector := database.SelectFields(group, set.NewSet("_id"))
updated := database.SelectFields(group, fields)
_, err = coll.UpdateOne(
db,
selector,
&bson.M{
"$set": updated,
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Get(db *database.Database, group string, key string) (
val interface{}, err error) {
coll := db.Settings()
grp := map[string]interface{}{}
err = coll.FindOne(
db,
&bson.M{
"_id": group,
},
options.FindOne().SetProjection(&bson.D{
{key, 1},
}),
).Decode(grp)
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
return
default:
err = &errortypes.DatabaseError{
errors.Wrap(err, "settings: Database error"),
}
return
}
}
val = grp[key]
return
}
func Set(db *database.Database, group string, key string, val interface{}) (
err error) {
coll := db.Settings()
_, err = coll.UpdateOne(
db,
&bson.M{
"_id": group,
},
&bson.M{
"$set": &bson.M{
key: val,
},
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Unset(db *database.Database, group string, key string) (
err error) {
coll := db.Settings()
_, err = coll.UpdateOne(
db,
&bson.M{
"_id": group,
},
&bson.M{
"$unset": &bson.M{
key: 1,
},
},
options.UpdateOne().SetUpsert(true),
)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func setDefaults(obj interface{}) {
val := reflect.ValueOf(obj)
elm := val.Elem()
n := elm.NumField()
for i := 0; i < n; i++ {
fld := elm.Field(i)
typ := elm.Type().Field(i)
if typ.PkgPath != "" {
continue
}
tag := typ.Tag.Get("default")
if tag == "" {
continue
}
switch fld.Kind() {
case reflect.Bool:
parVal, err := strconv.ParseBool(tag)
if err != nil {
panic(err)
}
fld.SetBool(parVal)
break
case reflect.Int, reflect.Int64:
if fld.Int() != 0 {
break
}
parVal, err := strconv.Atoi(tag)
if err != nil {
panic(err)
}
fld.SetInt(int64(parVal))
break
case reflect.String:
if fld.String() != "" {
break
}
fld.SetString(tag)
break
case reflect.Slice:
if fld.Len() != 0 {
break
}
sliceType := reflect.TypeOf(fld.Interface()).Elem()
vals := strings.Split(tag, ",")
n := len(vals)
slice := reflect.MakeSlice(reflect.SliceOf(sliceType), n, n)
switch sliceType.Kind() {
case reflect.Bool:
for i, val := range vals {
parVal, err := strconv.ParseBool(val)
if err != nil {
panic(err)
}
slice.Index(i).SetBool(parVal)
}
case reflect.Int:
for i, val := range vals {
parVal, err := strconv.Atoi(val)
if err != nil {
panic(err)
}
slice.Index(i).SetInt(int64(parVal))
}
case reflect.String:
for i, val := range vals {
slice.Index(i).SetString(val)
}
}
fld.Set(slice)
break
}
}
return
}
func Update(name string) (err error) {
db := database.GetDatabase()
defer db.Close()
coll := db.Settings()
group := registry[name]
data := group.New()
err = database.IgnoreNotFoundError(coll.FindOneId(name, data))
if err != nil {
return
}
setDefaults(data)
group.Update(data)
return
}
func update() {
for {
time.Sleep(10 * time.Second)
if constants.Shutdown {
return
}
for name := range registry {
err := Update(name)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("settings: Update error")
return
}
}
}
}
func init() {
module := requires.New("settings")
module.After("database")
module.Handler = func() (err error) {
for name := range registry {
err = Update(name)
if err != nil {
return
}
}
db := database.GetDatabase()
defer db.Close()
if System.DatabaseVersion == 0 {
System.DatabaseVersion = constants.DatabaseVersion
err = Commit(db, System, set.NewSet("database_version"))
if err != nil {
return
}
}
if System.DatabaseVersion > constants.DatabaseVersion {
logrus.WithFields(logrus.Fields{
"database_version": System.DatabaseVersion,
"software_version": constants.DatabaseVersion,
}).Error("settings: Database version newer then software")
err = &errortypes.DatabaseError{
errors.New(
"settings: Database version newer then software"),
}
return
} else if System.DatabaseVersion != constants.DatabaseVersion {
logrus.WithFields(logrus.Fields{
"database_version": System.DatabaseVersion,
"new_database_version": constants.DatabaseVersion,
}).Info("settings: Upgrading database version")
System.DatabaseVersion = constants.DatabaseVersion
err = Commit(db, System, set.NewSet("database_version"))
if err != nil {
return
}
}
if System.Name == "" {
System.Name = utils.RandName()
err = Commit(db, System, set.NewSet("name"))
if err != nil {
return
}
}
if Auth.Providers == nil {
Auth.Providers = []*Provider{}
err = Commit(db, Auth, set.NewSet("providers"))
if err != nil {
return
}
}
if Auth.SecondaryProviders == nil {
Auth.SecondaryProviders = []*SecondaryProvider{}
err = Commit(db, Auth, set.NewSet("secondary_providers"))
if err != nil {
return
}
}
go update()
return
}
}
================================================
FILE: settings/system.go
================================================
package settings
var System *system
type system struct {
Id string `bson:"_id"`
Name string `bson:"name"`
DatabaseVersion int `bson:"database_version"`
Demo bool `bson:"demo"`
License string `bson:"license"`
AdminCookieAuthKey []byte `bson:"admin_cookie_auth_key"`
AdminCookieCryptoKey []byte `bson:"admin_cookie_crypto_key"`
UserCookieAuthKey []byte `bson:"user_cookie_auth_key"`
UserCookieCryptoKey []byte `bson:"user_cookie_crypto_key"`
NodeTimestampTtl int `bson:"node_timestamp_ttl" default:"15"`
InstanceTimestampTtl int `bson:"instance_timestamp_ttl" default:"20"`
DomainLockTtl int `bson:"domain_lock_ttl" default:"30"`
DomainDeleteTtl int `bson:"domain_delete_ttl" default:"200"`
DomainRefreshTtl int `bson:"domain_refresh_ttl" default:"90"`
AcmeKeyAlgorithm string `bson:"acme_key_algorithm" default:"rsa"`
DiskBackupWindow int `bson:"disk_backup_window" default:"6"`
DiskBackupTime int `bson:"disk_backup_time" default:"10"`
PlannerBatchSize int `bson:"planner_batch_size" default:"10"`
NoMigrateRefresh bool `bson:"no_migrate_refresh"`
OracleApiRetryRate int `bson:"oracle_api_retry_rate" default:"1"`
OracleApiRetryCount int `bson:"oracle_api_retry_count" default:"120"`
TwilioAccount string `bson:"twilio_account"`
TwilioSecret string `bson:"twilio_secret"`
TwilioNumber string `bson:"twilio_number"`
}
func newSystem() interface{} {
return &system{
Id: "system",
}
}
func updateSystem(data interface{}) {
System = data.(*system)
}
func init() {
register("system", newSystem, updateSystem)
}
================================================
FILE: settings/telemetry.go
================================================
package settings
var Telemetry *telemetry
type telemetry struct {
Id string `bson:"_id"`
NvdTtl int `bson:"nvd_ttl" default:"21600"`
NvdFinalTtl int `bson:"nvd_final_ttl" default:"604800"`
NvdApiLimit int `bson:"nvd_api_limit" default:"8"`
NvdApiAuthLimit int `bson:"nvd_api_auth_limit" default:"1"`
NvdApiKey string `bson:"nvd_api_key"`
}
func newTelemetry() interface{} {
return &telemetry{
Id: "telemetry",
}
}
func updateTelemetry(data interface{}) {
Telemetry = data.(*telemetry)
}
func init() {
register("telemetry", newTelemetry, updateTelemetry)
}
================================================
FILE: setup/iptables.go
================================================
package setup
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/ipset"
"github.com/pritunl/pritunl-cloud/iptables"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
)
func Iptables() (err error) {
db := database.GetDatabase()
defer db.Close()
namespaces, err := utils.GetNamespaces()
if err != nil {
return
}
nodeDatacenter, err := node.Self.GetDatacenter(db)
if err != nil {
return
}
vpcs := []*vpc.Vpc{}
if !nodeDatacenter.IsZero() {
vpcs, err = vpc.GetDatacenter(db, nodeDatacenter)
if err != nil {
return
}
}
instances, err := instance.GetAllVirt(db, &bson.M{
"node": node.Self.Id,
}, nil, nil)
if err != nil {
return
}
specRules, nodePortsMap, err := firewall.GetSpecRulesSlow(
db, node.Self.Id, instances)
if err != nil {
return
}
nodeFirewall, firewalls, firewallMaps, _, err := firewall.GetAllIngress(
db, node.Self, instances, specRules, nodePortsMap)
if err != nil {
return
}
err = ipset.Init(namespaces, instances, nodeFirewall, firewalls)
if err != nil {
return
}
err = iptables.Init(namespaces, vpcs, instances, nodeFirewall,
firewalls, firewallMaps)
if err != nil {
return
}
err = ipset.InitNames(namespaces, instances, nodeFirewall, firewalls)
if err != nil {
return
}
return
}
================================================
FILE: shape/constants.go
================================================
package shape
const (
Instance = "instance"
Qcow2 = "qcow2"
Lvm = "lvm"
)
================================================
FILE: shape/node.go
================================================
package shape
import (
"sort"
"github.com/pritunl/pritunl-cloud/node"
)
type Nodes []*node.Node
func (n Nodes) Len() int {
return len(n)
}
func (n Nodes) Less(i, j int) bool {
return n[i].Usage() < n[j].Usage()
}
func (n Nodes) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func (n Nodes) Sort() {
sort.Sort(n)
}
================================================
FILE: shape/shape.go
================================================
package shape
import (
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/zone"
)
type Shape struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Type string `bson:"type" json:"type"`
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
Roles []string `bson:"roles" json:"roles"`
Flexible bool `bson:"flexible" json:"flexible"`
DiskType string `bson:"disk_type" json:"disk_type"`
DiskPool bson.ObjectID `bson:"disk_pool" json:"disk_pool"`
Memory int `bson:"memory" json:"memory"`
Processors int `bson:"processors" json:"processors"`
NodeCount int `bson:"-" json:"node_count"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
Flexible bool `bson:"flexible" json:"flexible"`
Memory int `bson:"memory" json:"memory"`
Processors int `bson:"processors" json:"processors"`
}
func (s *Shape) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
s.Name = utils.FilterName(s.Name)
if s.Type == "" {
s.Type = Instance
}
switch s.Type {
case Instance:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_shape_type",
Message: "Shape type invalid",
}
return
}
if s.Roles == nil {
s.Roles = []string{}
}
switch s.DiskType {
case "", Qcow2:
s.DiskType = Qcow2
break
case Lvm:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_disk_type",
Message: "Disk type invalid",
}
return
}
if s.Datacenter.IsZero() {
errData = &errortypes.ErrorData{
Error: "missing_datacenter",
Message: "Shape datacenter required",
}
return
}
return
}
func (s *Shape) FindNode(db *database.Database, processors, memory int) (
nde *node.Node, err error) {
zones, err := zone.GetAllDatacenter(db, s.Datacenter)
if err != nil {
return
}
zoneIds := []bson.ObjectID{}
for _, zne := range zones {
zoneIds = append(zoneIds, zne.Id)
}
ndes, err := node.GetAllShape(db, zoneIds, s.Roles)
if err != nil {
return
}
Nodes(ndes).Sort()
for _, nd := range ndes {
nde = nd
return
}
err = &errortypes.NotFoundError{
errors.New("shape: Failed to find available node"),
}
return
}
func (s *Shape) Commit(db *database.Database) (err error) {
coll := db.Shapes()
err = coll.Commit(s.Id, s)
if err != nil {
return
}
return
}
func (s *Shape) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Shapes()
err = coll.CommitFields(s.Id, s, fields)
if err != nil {
return
}
return
}
func (s *Shape) Insert(db *database.Database) (err error) {
coll := db.Shapes()
_, err = coll.InsertOne(db, s)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: shape/utils.go
================================================
package shape
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, shapeId bson.ObjectID) (
shpe *Shape, err error) {
coll := db.Shapes()
shpe = &Shape{}
err = coll.FindOneId(shapeId, shpe)
if err != nil {
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (shpe *Shape, err error) {
coll := db.Shapes()
shpe = &Shape{}
err = coll.FindOne(db, query).Decode(shpe)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
shapes []*Shape, err error) {
coll := db.Shapes()
shapes = []*Shape{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
nde := &Shape{}
err = cursor.Decode(nde)
if err != nil {
err = database.ParseError(err)
return
}
shapes = append(shapes, nde)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (shapes []*Shape, count int64, err error) {
coll := db.Shapes()
shapes = []*Shape{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
shpe := &Shape{}
err = cursor.Decode(shpe)
if err != nil {
err = database.ParseError(err)
return
}
shapes = append(shapes, shpe)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNames(db *database.Database, query *bson.M) (
shapes []*Shape, err error) {
coll := db.Shapes()
shapes = []*Shape{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(&bson.D{
{"name", 1},
}).
SetProjection(&bson.D{
{"_id", 1},
{"name", 1},
{"type", 1},
{"zone", 1},
{"flexible", 1},
{"disk_type", 1},
{"disk_pool", 1},
{"memory", 1},
{"processors", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
shpe := &Shape{}
err = cursor.Decode(shpe)
if err != nil {
err = database.ParseError(err)
return
}
shapes = append(shapes, shpe)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, shapeId bson.ObjectID) (err error) {
coll := db.Shapes()
_, err = coll.DeleteOne(db, &bson.M{
"_id": shapeId,
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, shapeIds []bson.ObjectID) (
err error) {
coll := db.Shapes()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": shapeIds,
},
"delete_protection": false,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: signature/signature.go
================================================
package signature
import (
"crypto/hmac"
"crypto/sha512"
"crypto/subtle"
"encoding/base64"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/nonce"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
"github.com/pritunl/pritunl-cloud/utils"
)
type Signature struct {
Token string
Nonce string
Timestamp time.Time
Signature string
Method string
Path string
user *user.User
}
func (s *Signature) GetUser(db *database.Database) (
usr *user.User, err error) {
if s.user != nil || db == nil || s.Token == "" {
usr = s.user
return
}
usr, err = user.GetTokenUpdate(db, s.Token)
if err != nil {
return
}
s.user = usr
return
}
func (s *Signature) Validate(db *database.Database) (err error) {
if s.Token == "" {
err = &errortypes.AuthenticationError{
errors.New("signature: Invalid authentication token"),
}
return
}
if len(s.Nonce) < 16 || len(s.Nonce) > 128 {
err = &errortypes.AuthenticationError{
errors.New("signature: Invalid authentication nonce"),
}
return
}
if utils.SinceAbs(s.Timestamp) > time.Duration(
settings.Auth.Window)*time.Second {
err = &errortypes.AuthenticationError{
errors.New("signature: Authentication timestamp outside window"),
}
return
}
usr, err := s.GetUser(db)
if err != nil {
switch err.(type) {
case *database.NotFoundError:
usr = nil
err = nil
break
default:
return
}
}
if usr == nil || usr.Type != user.Api ||
usr.Token == "" || usr.Secret == "" {
err = &errortypes.AuthenticationError{
errors.New("signature: User not found"),
}
return
}
authString := strings.Join([]string{
usr.Token,
strconv.FormatInt(s.Timestamp.Unix(), 10),
s.Nonce,
s.Method,
s.Path,
}, "&")
err = nonce.Validate(db, s.Nonce)
if err != nil {
return
}
hashFunc := hmac.New(sha512.New, []byte(usr.Secret))
hashFunc.Write([]byte(authString))
rawSignature := hashFunc.Sum(nil)
sig := base64.StdEncoding.EncodeToString(rawSignature)
if subtle.ConstantTimeCompare([]byte(s.Signature), []byte(sig)) != 1 {
err = &errortypes.AuthenticationError{
errors.New("signature: Invalid signature"),
}
return
}
return
}
================================================
FILE: signature/utils.go
================================================
package signature
import (
"strconv"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func Parse(token, sigStr, timeStr, nonce, method, path string) (
sig *Signature, err error) {
timestampInt, _ := strconv.ParseInt(timeStr, 10, 64)
if timestampInt == 0 {
err = &errortypes.AuthenticationError{
errors.New("signature: Invalid authentication timestamp"),
}
return
}
timestamp := time.Unix(timestampInt, 0)
sig = &Signature{
Token: token,
Nonce: nonce,
Timestamp: timestamp,
Signature: sigStr,
Method: method,
Path: path,
}
return
}
================================================
FILE: spec/constants.go
================================================
package spec
import (
"regexp"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
var resourcesRe = regexp.MustCompile("(?s)```yaml(.*?)```")
const (
All = "all"
Icmp = "icmp"
Tcp = "tcp"
Udp = "udp"
Multicast = "multicast"
Broadcast = "broadcast"
Host = "host"
Private = "private"
Private6 = "private6"
Public = "public"
Public6 = "public6"
CloudPublic = "cloud_public"
CloudPublic6 = "cloud_public6"
CloudPrivate = "cloud_private"
Systemd = "systemd"
File = "file"
TokenPrefix = "+/"
Disk = "disk"
HostPath = "host_path"
)
type Base struct {
Kind string `yaml:"kind"`
}
const (
Unit = "unit"
)
type Refrence struct {
Id bson.ObjectID `bson:"id" json:"id"`
Realm bson.ObjectID `bson:"realm" json:"realm"`
Kind string `bson:"kind" json:"kind"`
Selector string `bson:"selector" json:"selector"`
}
================================================
FILE: spec/domain.go
================================================
package spec
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Domain struct {
Records []*Record `bson:"records" json:"records"`
}
func (d *Domain) Validate() (errData *errortypes.ErrorData, err error) {
for _, rec := range d.Records {
rec.Name = utils.FilterDomain(rec.Name)
switch rec.Type {
case Host:
break
case Private:
break
case Private6:
break
case Public:
break
case Public6:
break
case CloudPublic:
break
case CloudPrivate:
break
default:
errData = &errortypes.ErrorData{
Error: "unknown_domain_record_type",
Message: "Unknown domain record type",
}
return
}
}
return
}
type Record struct {
Name string `bson:"name" json:"name"`
Domain bson.ObjectID `bson:"domain" json:"domain"`
Type string `bson:"type" json:"type"`
}
type DomainYaml struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Records []DomainYamlRecord `yaml:"records"`
}
type DomainYamlRecord struct {
Name string `yaml:"name"`
Domain string `yaml:"domain"`
Type string `yaml:"type"`
}
================================================
FILE: spec/firewall.go
================================================
package spec
import (
"net"
"strconv"
"strings"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type Firewall struct {
Ingress []*Rule `bson:"ingress" json:"ingress"`
}
type Rule struct {
Protocol string `bson:"protocol" json:"protocol"`
Port string `bson:"port" json:"port"`
SourceIps []string `bson:"source_ips" json:"source_ips"`
Sources []*Refrence `bson:"sources" json:"sources"`
}
func (f *Firewall) Validate() (errData *errortypes.ErrorData, err error) {
if f.Ingress == nil {
f.Ingress = []*Rule{}
}
for _, rule := range f.Ingress {
switch rule.Protocol {
case All:
rule.Port = ""
break
case Icmp:
rule.Port = ""
break
case Tcp, Udp, Multicast, Broadcast:
ports := strings.Split(rule.Port, "-")
portInt, e := strconv.Atoi(ports[0])
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
if portInt < 1 || portInt > 65535 {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
parsedPort := strconv.Itoa(portInt)
if len(ports) > 1 {
portInt2, e := strconv.Atoi(ports[1])
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
if portInt < 1 || portInt > 65535 || portInt2 <= portInt {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_port",
Message: "Invalid ingress rule port",
}
return
}
parsedPort += "-" + strconv.Itoa(portInt2)
}
rule.Port = parsedPort
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_protocol",
Message: "Invalid ingress rule protocol",
}
return
}
if rule.Sources == nil {
rule.Sources = []*Refrence{}
}
if rule.SourceIps == nil {
rule.SourceIps = []string{}
}
for i, sourceIp := range rule.SourceIps {
if sourceIp == "" {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_source_ip",
Message: "Empty ingress rule source IP",
}
return
}
if !strings.Contains(sourceIp, "/") {
if strings.Contains(sourceIp, ":") {
sourceIp += "/128"
} else {
sourceIp += "/32"
}
}
_, sourceCidr, e := net.ParseCIDR(sourceIp)
if e != nil {
errData = &errortypes.ErrorData{
Error: "invalid_ingress_rule_source_ip",
Message: "Invalid ingress rule source IP",
}
return
}
rule.SourceIps[i] = sourceCidr.String()
}
if rule.Protocol == Multicast || rule.Protocol == Broadcast {
rule.Sources = []*Refrence{}
rule.SourceIps = []string{}
}
}
return
}
type FirewallYaml struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Ingress []FirewallYamlIngress `yaml:"ingress"`
}
type FirewallYamlIngress struct {
Protocol string `yaml:"protocol"`
Port string `yaml:"port"`
Source []string `yaml:"source"`
}
================================================
FILE: spec/instance.go
================================================
package spec
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/nodeport"
)
type Instance struct {
Plan bson.ObjectID `bson:"plan,omitempty" json:"plan"` // clear
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"` // hard
Zone bson.ObjectID `bson:"zone" json:"zone"` // hard
Node bson.ObjectID `bson:"node,omitempty" json:"node"` // hard
Shape bson.ObjectID `bson:"shape,omitempty" json:"shape"` // hard
Vpc bson.ObjectID `bson:"vpc" json:"vpc"` // hard
Subnet bson.ObjectID `bson:"subnet" json:"subnet"` // hard
Roles []string `bson:"roles" json:"roles"` // soft
Processors int `bson:"processors" json:"processors"` // soft
Memory int `bson:"memory" json:"memory"` // soft
Uefi *bool `bson:"uefi,omitempty" json:"uefi"` // soft
SecureBoot *bool `bson:"secure_boot,omitempty" json:"secure_boot"` // soft
CloudType string `bson:"cloud_type" json:"cloud_type"` // soft
Tpm bool `bson:"tpm" json:"tpm"` // soft
Vnc bool `bson:"vnc" json:"vnc"` // soft
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"` // soft
SkipSourceDestCheck bool `bson:"skip_source_dest_check" json:"skip_source_dest_check"` // soft
Gui bool `bson:"gui" json:"gui"` // soft
HostAddress *bool `bson:"host_address,omitempty" json:"host_address"` // soft
PublicAddress *bool `bson:"public_address,omitempty" json:"public_address"` // soft
PublicAddress6 *bool `bson:"public_address6,omitempty" json:"public_address6"` // soft
DhcpServer bool `bson:"dhcp_server" json:"dhcp_server"` // soft
Image bson.ObjectID `bson:"image" json:"image"` // hard
DiskSize int `bson:"disk_size" json:"disk_size"` // hard
Mounts []Mount `bson:"mounts" json:"mounts"` // hard
NodePorts []NodePort `bson:"node_ports" json:"node_ports"` // soft
Certificates []bson.ObjectID `bson:"certificates" json:"certificates"` // soft
Secrets []bson.ObjectID `bson:"secrets" json:"secrets"` // soft
Pods []bson.ObjectID `bson:"pods" json:"pods"` // soft
}
type NodePort struct {
Protocol string `bson:"protocol" json:"protocol"`
ExternalPort int `bson:"external_port" json:"external_port"`
InternalPort int `bson:"internal_port" json:"internal_port"`
}
func (m *NodePort) Validate() (
errData *errortypes.ErrorData, err error) {
switch m.Protocol {
case Tcp, Udp:
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_protocol",
Message: "Invalid node port protocol",
}
return
}
portRanges, e := nodeport.GetPortRanges()
if e != nil {
err = e
return
}
matched := false
for _, ports := range portRanges {
if ports.Contains(m.ExternalPort) {
matched = true
break
}
}
if !matched {
errData = &errortypes.ErrorData{
Error: "invalid_external_port",
Message: "Invalid external node port",
}
return
}
if m.InternalPort <= 0 || m.InternalPort > 65535 {
errData = &errortypes.ErrorData{
Error: "invalid_internal_port",
Message: "Invalid internal node port",
}
return
}
return
}
func (i *Instance) DiffNodePorts(newNodePorts []NodePort) bool {
if len(i.NodePorts) != len(newNodePorts) {
return true
}
for x := range i.NodePorts {
if i.NodePorts[x].Protocol != newNodePorts[x].Protocol ||
i.NodePorts[x].ExternalPort != newNodePorts[x].ExternalPort ||
i.NodePorts[x].InternalPort != newNodePorts[x].InternalPort {
return true
}
}
return false
}
func (i *Instance) MemoryUnits() float64 {
return float64(i.Memory) / float64(1024)
}
type Mount struct {
Name string `bson:"name" json:"name"`
Type string `bson:"type" json:"type"`
Path string `bson:"path" json:"path"`
HostPath string `bson:"host_path" json:"host_path"`
Disks []bson.ObjectID `bson:"disks" json:"disks"`
}
type InstanceYaml struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Count int `yaml:"count"`
Plan string `yaml:"plan"`
Zone string `yaml:"zone"`
Node string `yaml:"node,omitempty"`
Shape string `yaml:"shape,omitempty"`
Vpc string `yaml:"vpc"`
Subnet string `yaml:"subnet"`
Roles []string `yaml:"roles"`
Processors int `yaml:"processors"`
Memory int `yaml:"memory"`
Uefi *bool `yaml:"uefi"`
SecureBoot *bool `yaml:"secureBoot"`
CloudType string `yaml:"cloudType"`
Tpm bool `yaml:"tpm"`
Vnc bool `yaml:"vnc"`
DeleteProtection bool `yaml:"deleteProtection"`
SkipSourceDestCheck bool `yaml:"skipSourceDestCheck"`
Gui bool `yaml:"gui"`
HostAddress *bool `yaml:"hostAddress"`
PublicAddress *bool `yaml:"publicAddress"`
PublicAddress6 *bool `yaml:"publicAddress6"`
DhcpServer bool `yaml:"dhcpServer"`
Image string `yaml:"image"`
Mounts []InstanceMountYaml `yaml:"mounts"`
NodePorts []InstanceNodePortYaml `yaml:"nodePorts"`
Certificates []string `yaml:"certificates"`
Secrets []string `yaml:"secrets"`
Pods []string `yaml:"pods"`
DiskSize int `yaml:"diskSize"`
}
type InstanceMountYaml struct {
Name string `yaml:"name"`
Type string `yaml:"type"`
Path string `yaml:"path"`
HostPath string `yaml:"hostPath"`
Disks []string `yaml:"disks"`
}
type InstanceNodePortYaml struct {
Protocol string `yaml:"protocol"`
ExternalPort int `yaml:"externalPort"`
InternalPort int `yaml:"internalPort"`
}
================================================
FILE: spec/journal.go
================================================
package spec
import (
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Journal struct {
Inputs []*Input `bson:"inputs" json:"inputs"`
}
type Input struct {
Index int32 `bson:"index" json:"index"`
Key string `bson:"key" json:"key"`
Type string `bson:"type" json:"type"`
Unit string `bson:"unit" json:"unit"`
Path string `bson:"path" json:"path"`
}
func (j *Journal) Validate() (errData *errortypes.ErrorData, err error) {
for _, input := range j.Inputs {
if input.Key == "" {
errData = &errortypes.ErrorData{
Error: "journal_key_missing",
Message: "Missing journal key",
}
return
}
key := utils.FilterName(input.Key)
if input.Key != key {
errData = &errortypes.ErrorData{
Error: "journal_key_invalid",
Message: "Journal key invalid",
}
return
}
input.Key = key
switch input.Type {
case Systemd:
input.Path = ""
if input.Unit == "" {
errData = &errortypes.ErrorData{
Error: "systemd_unit_missing",
Message: "Missing systemd unit",
}
return
}
inputUnit := utils.FilterUnit(input.Unit)
if input.Unit != inputUnit {
errData = &errortypes.ErrorData{
Error: "systemd_unit_invalid",
Message: "Invalid systemd unit",
}
return
}
input.Unit = inputUnit
break
case File:
input.Unit = ""
if input.Path == "" {
errData = &errortypes.ErrorData{
Error: "log_path_missing",
Message: "Missing log path",
}
return
}
input.Path = utils.FilterPath(input.Path)
if input.Path == "" {
errData = &errortypes.ErrorData{
Error: "log_path_invalid",
Message: "Invalid log path",
}
return
}
break
default:
errData = &errortypes.ErrorData{
Error: "unknown_input_type",
Message: "Unknown input type",
}
return
}
}
return
}
type JournalYaml struct {
Name string `yaml:"name"`
Kind string `yaml:"kind"`
Inputs []JournalYamlInput `yaml:"inputs"`
}
type JournalYamlInput struct {
Key string `yaml:"key"`
Type string `yaml:"type"`
Unit string `yaml:"unit,omitempty"`
Path string `yaml:"path,omitempty"`
}
================================================
FILE: spec/node.go
================================================
package spec
import (
"sort"
"github.com/pritunl/pritunl-cloud/node"
)
type Nodes []*node.Node
func (n Nodes) Len() int {
return len(n)
}
func (n Nodes) Less(i, j int) bool {
return n[i].Usage() < n[j].Usage()
}
func (n Nodes) Swap(i, j int) {
n[i], n[j] = n[j], n[i]
}
func (n Nodes) Sort() {
sort.Sort(n)
}
================================================
FILE: spec/spec.go
================================================
package spec
import (
"crypto/sha1"
"fmt"
"io"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/finder"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/journal"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/organization"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/shape"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/zone"
"gopkg.in/yaml.v2"
)
type Spec struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Pod bson.ObjectID `bson:"pod" json:"pod"`
Unit bson.ObjectID `bson:"unit" json:"unit"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Index int `bson:"index" json:"index"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
Name string `bson:"name" json:"name"`
Kind string `bson:"kind" json:"kind"`
Count int `bson:"count" json:"count"`
Hash string `bson:"hash" json:"hash"`
Data string `bson:"data" json:"data"`
Instance *Instance `bson:"instance,omitempty" json:"-"`
Firewall *Firewall `bson:"firewall,omitempty" json:"-"`
Domain *Domain `bson:"domain,omitempty" json:"-"`
Journal *Journal `bson:"journal,omitempty" json:"-"`
}
func (s *Spec) GetAllNodes(db *database.Database) (ndes Nodes,
offlineCount, noMountCount int, err error) {
org, err := organization.Get(db, s.Organization)
if err != nil {
return
}
shpe, err := shape.Get(db, s.Instance.Shape)
if err != nil {
return
}
zones, err := zone.GetAllDatacenter(db, shpe.Datacenter)
if err != nil {
return
}
zoneIds := []bson.ObjectID{}
for _, zne := range zones {
zoneIds = append(zoneIds, zne.Id)
}
allNdes, err := node.GetAllShape(db, zoneIds, shpe.Roles)
if err != nil {
return
}
var mountNodes []set.Set
if len(s.Instance.Mounts) > 0 {
diskIds := []bson.ObjectID{}
for _, mount := range s.Instance.Mounts {
if mount.Type != Disk {
continue
}
diskIds = append(diskIds, mount.Disks...)
}
disksMap := map[bson.ObjectID]*disk.Disk{}
if len(diskIds) > 0 {
disksMap, err = disk.GetAllMap(db, &bson.M{
"_id": &bson.M{
"$in": diskIds,
},
"organization": s.Organization,
})
if err != nil {
return
}
}
for _, mount := range s.Instance.Mounts {
mountSet := set.NewSet()
if mount.Type == Disk {
for _, dskId := range mount.Disks {
dsk := disksMap[dskId]
if dsk == nil || !dsk.IsAvailable() {
continue
}
mountSet.Add(dsk.Node)
}
} else if mount.Type == HostPath {
for _, nde := range allNdes {
for _, share := range nde.Shares {
if share.MatchPath(mount.HostPath) &&
utils.HasMatchingItem(share.Roles, org.Roles) {
mountSet.Add(nde.Id)
}
}
}
}
mountNodes = append(mountNodes, mountSet)
}
}
ndes = Nodes{}
for _, nde := range allNdes {
if !nde.IsOnline() {
offlineCount += 1
continue
}
if mountNodes != nil {
match := true
for _, mountSet := range mountNodes {
if !mountSet.Contains(nde.Id) {
match = false
break
}
}
if !match {
noMountCount += 1
continue
}
}
ndes = append(ndes, nde)
}
return
}
func (s *Spec) ExtractResources() (resources string, err error) {
matches := resourcesRe.FindStringSubmatch(s.Data)
if len(matches) > 1 {
resources = matches[1]
resources = strings.TrimSpace(resources)
return
}
return
}
func (s *Spec) parseFirewall(db *database.Database,
orgId bson.ObjectID, dataYaml *FirewallYaml) (
errData *errortypes.ErrorData, err error) {
data := &Firewall{
Ingress: []*Rule{},
}
if dataYaml.Kind != finder.FirewallKind {
errData = &errortypes.ErrorData{
Error: "unit_kind_mismatch",
Message: "Unit kind unexpected",
}
return
}
resources := &finder.Resources{
Organization: orgId,
}
for _, ruleYaml := range dataYaml.Ingress {
if ruleYaml.Source == nil {
continue
}
rule := &Rule{
Protocol: ruleYaml.Protocol,
Port: ruleYaml.Port,
}
refs := set.NewSet()
for _, source := range ruleYaml.Source {
if strings.HasPrefix(source, TokenPrefix) {
kind, e := resources.Find(db, source)
if e != nil {
err = e
return
}
if kind == finder.UnitKind && resources.Unit != nil {
selector := resources.Selector
if selector == "" {
selector = "private_ips"
}
refs.Add(Refrence{
Id: resources.Unit.Id,
Realm: resources.Unit.Pod,
Kind: Unit,
Selector: selector,
})
}
} else {
rule.SourceIps = append(rule.SourceIps, source)
}
}
for refInf := range refs.Iter() {
ref := refInf.(Refrence)
rule.Sources = append(rule.Sources, &ref)
}
data.Ingress = append(data.Ingress, rule)
}
errData, err = data.Validate()
if err != nil || errData != nil {
return
}
s.Firewall = data
return
}
func (s *Spec) parseInstance(db *database.Database,
orgId bson.ObjectID, dataYaml *InstanceYaml) (
errData *errortypes.ErrorData, err error) {
data := &Instance{}
var shpe *shape.Shape
if dataYaml.Name == "" {
errData = &errortypes.ErrorData{
Error: "unit_name_missing",
Message: "Unit name is missing",
}
return
}
switch dataYaml.Kind {
case finder.InstanceKind:
break
case finder.ImageKind:
break
default:
errData = &errortypes.ErrorData{
Error: "unit_kind_mismatch",
Message: "Unit kind unexpected",
}
return
}
resources := &finder.Resources{
Organization: orgId,
}
if dataYaml.Plan != "" {
kind, e := resources.Find(db, dataYaml.Plan)
if e != nil {
err = e
return
}
if kind == finder.PlanKind && resources.Plan != nil {
data.Plan = resources.Plan.Id
}
}
if dataYaml.Zone != "" {
kind, e := resources.Find(db, dataYaml.Zone)
if e != nil {
err = e
return
}
if kind == finder.ZoneKind && resources.Zone != nil {
data.Datacenter = resources.Datacenter.Id
data.Zone = resources.Zone.Id
}
}
if data.Zone.IsZero() {
errData = &errortypes.ErrorData{
Error: "unit_zone_missing",
Message: "Unit zone is missing",
}
return
}
if dataYaml.Node != "" {
kind, e := resources.Find(db, dataYaml.Node)
if e != nil {
err = e
return
}
if kind == finder.NodeKind && resources.Node != nil {
data.Node = resources.Node.Id
}
}
if dataYaml.Shape != "" {
kind, e := resources.Find(db, dataYaml.Shape)
if e != nil {
err = e
return
}
if kind == finder.ShapeKind && resources.Shape != nil {
shpe = resources.Shape
data.Shape = resources.Shape.Id
}
}
if data.Node.IsZero() && data.Shape.IsZero() {
errData = &errortypes.ErrorData{
Error: "unit_node_missing",
Message: "Unit node or shape is missing",
}
return
}
if dataYaml.Vpc != "" {
kind, e := resources.Find(db, dataYaml.Vpc)
if e != nil {
err = e
return
}
if kind == finder.VpcKind && resources.Vpc != nil {
data.Vpc = resources.Vpc.Id
}
}
if data.Vpc.IsZero() {
errData = &errortypes.ErrorData{
Error: "unit_vpc_missing",
Message: "Unit VPC is missing",
}
return
}
if dataYaml.Subnet != "" {
kind, e := resources.Find(db, dataYaml.Subnet)
if e != nil {
err = e
return
}
if kind == finder.SubnetKind && resources.Subnet != nil {
data.Subnet = resources.Subnet.Id
}
}
if data.Subnet.IsZero() {
errData = &errortypes.ErrorData{
Error: "unit_subnet_missing",
Message: "Unit subnet is missing",
}
return
}
if dataYaml.Image != "" {
kind, e := resources.Find(db, dataYaml.Image)
if e != nil {
err = e
return
}
if kind == finder.ImageKind && resources.Image != nil {
data.Image = resources.Image.Id
}
if kind == finder.BuildKind && resources.Deployment != nil &&
resources.Deployment.ImageReady() {
data.Image = resources.Deployment.Image
}
}
if dataYaml.Mounts != nil {
for _, mount := range dataYaml.Mounts {
mnt := Mount{
Path: utils.FilterPath(mount.Path),
Disks: []bson.ObjectID{},
}
if mnt.Path == "" {
errData = &errortypes.ErrorData{
Error: "mount_path_missing",
Message: "Unit mount path is missing",
}
return
}
if mount.Type == HostPath {
mnt.Name = mount.Name
mnt.Type = HostPath
mnt.HostPath = utils.FilterPath(mount.HostPath)
if mnt.Name == "" {
errData = &errortypes.ErrorData{
Error: "mount_name_missing",
Message: "Unit mount name is missing",
}
return
}
if mnt.HostPath == "" {
errData = &errortypes.ErrorData{
Error: "mount_host_path_missing",
Message: "Unit mount hostPath is missing",
}
return
}
} else if mount.Type == Disk || mount.Type == "" {
mnt.Type = Disk
if mnt.Path == "" {
errData = &errortypes.ErrorData{
Error: "mount_path_missing",
Message: "Unit mount path is missing",
}
return
}
for _, dsk := range mount.Disks {
kind, e := resources.Find(db, dsk)
if e != nil {
err = e
return
}
if kind == finder.DiskKind && resources.Disks != nil {
for _, dskRes := range resources.Disks {
mnt.Disks = append(mnt.Disks, dskRes.Id)
}
}
}
} else {
errData = &errortypes.ErrorData{
Error: "mount_type_invalid",
Message: "Unit mount type is invalid",
}
return
}
data.Mounts = append(data.Mounts, mnt)
}
}
if dataYaml.NodePorts != nil {
externalNodePorts := set.NewSet()
for _, nodePrt := range dataYaml.NodePorts {
mapping := NodePort{
Protocol: nodePrt.Protocol,
ExternalPort: nodePrt.ExternalPort,
InternalPort: nodePrt.InternalPort,
}
extPortKey := fmt.Sprintf("%s:%d",
mapping.Protocol, mapping.ExternalPort)
if externalNodePorts.Contains(extPortKey) {
errData = &errortypes.ErrorData{
Error: "node_port_external_duplicate",
Message: "Duplicate external node port",
}
return
}
externalNodePorts.Add(extPortKey)
errData, err = mapping.Validate()
if err != nil || errData != nil {
return
}
data.NodePorts = append(data.NodePorts, mapping)
}
}
if dataYaml.Certificates != nil {
for _, cert := range dataYaml.Certificates {
kind, e := resources.Find(db, cert)
if e != nil {
err = e
return
}
if kind == finder.CertificateKind && resources.Certificate != nil {
data.Certificates = append(
data.Certificates,
resources.Certificate.Id,
)
}
}
}
if dataYaml.Secrets != nil {
for _, cert := range dataYaml.Secrets {
kind, e := resources.Find(db, cert)
if e != nil {
err = e
return
}
if kind == finder.SecretKind && resources.Secret != nil {
data.Secrets = append(
data.Secrets,
resources.Secret.Id,
)
}
}
}
if dataYaml.Pods != nil {
for _, cert := range dataYaml.Pods {
kind, e := resources.Find(db, cert)
if e != nil {
err = e
return
}
if kind == finder.PodKind && resources.Pod != nil {
data.Pods = append(
data.Pods,
resources.Pod.Id,
)
}
}
}
if data.Node.IsZero() && data.Shape.IsZero() {
errData = &errortypes.ErrorData{
Error: "unit_image_missing",
Message: "Unit image is missing",
}
return
}
if shpe != nil {
data.Processors = shpe.Processors
data.Memory = shpe.Memory
if shpe.Flexible {
if dataYaml.Processors != 0 {
data.Processors = dataYaml.Processors
}
if dataYaml.Memory != 0 {
data.Memory = dataYaml.Memory
}
}
} else {
data.Processors = dataYaml.Processors
data.Memory = dataYaml.Memory
}
data.Uefi = dataYaml.Uefi
data.SecureBoot = dataYaml.SecureBoot
switch dataYaml.CloudType {
case instance.Linux:
data.CloudType = instance.Linux
break
case instance.LinuxLegacy:
data.CloudType = instance.LinuxLegacy
break
case instance.BSD:
data.CloudType = instance.BSD
break
case "":
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_unit_cloud_type",
Message: "Unit instance cloud type is invalid",
}
return
}
data.Tpm = dataYaml.Tpm
data.Vnc = dataYaml.Vnc
data.DeleteProtection = dataYaml.DeleteProtection
data.SkipSourceDestCheck = dataYaml.SkipSourceDestCheck
data.Gui = dataYaml.Gui
data.HostAddress = dataYaml.HostAddress
data.PublicAddress = dataYaml.PublicAddress
data.PublicAddress6 = dataYaml.PublicAddress6
data.DhcpServer = dataYaml.DhcpServer
data.Roles = dataYaml.Roles
data.DiskSize = dataYaml.DiskSize
s.Name = dataYaml.Name
s.Kind = dataYaml.Kind
s.Count = dataYaml.Count
s.Instance = data
s.Count = dataYaml.Count
if s.Kind == finder.ImageKind && s.Count != 0 {
errData = &errortypes.ErrorData{
Error: "count_invalid",
Message: "Count not valid for image kind",
}
return
}
return
}
func (s *Spec) parseDomain(db *database.Database,
orgId bson.ObjectID, dataYaml *DomainYaml) (
errData *errortypes.ErrorData, err error) {
data := &Domain{
Records: []*Record{},
}
if dataYaml.Kind != finder.DomainKind {
errData = &errortypes.ErrorData{
Error: "unit_kind_mismatch",
Message: "Unit kind unexpected",
}
return
}
resources := &finder.Resources{
Organization: orgId,
}
for _, recordYaml := range dataYaml.Records {
if recordYaml.Name == "" || recordYaml.Type == "" {
continue
}
record := &Record{
Name: utils.FilterName(recordYaml.Name),
Type: recordYaml.Type,
}
kind, e := resources.Find(db, recordYaml.Domain)
if e != nil {
err = e
return
}
if kind == finder.DomainKind && resources.Domain != nil {
record.Domain = resources.Domain.Id
}
data.Records = append(data.Records, record)
}
errData, err = data.Validate()
if err != nil || errData != nil {
return
}
s.Domain = data
return
}
func (s *Spec) parseJournal(db *database.Database,
jrnlKindGen journal.KindGenerator, dataYaml *JournalYaml) (
errData *errortypes.ErrorData, err error) {
data := &Journal{
Inputs: []*Input{},
}
if dataYaml.Kind != finder.JournalKind {
errData = &errortypes.ErrorData{
Error: "unit_kind_mismatch",
Message: "Unit kind unexpected",
}
return
}
curIndexes := map[string]int32{}
if s.Journal != nil {
for _, jrnl := range s.Journal.Inputs {
curIndexes[jrnl.Key] = jrnl.Index
}
}
for _, input := range dataYaml.Inputs {
data.Inputs = append(data.Inputs, &Input{
Key: input.Key,
Type: input.Type,
Unit: input.Unit,
Path: input.Path,
})
}
errData, err = data.Validate()
if err != nil || errData != nil {
return
}
for _, input := range data.Inputs {
input.Index = curIndexes[input.Key]
if input.Index == 0 {
if jrnlKindGen == nil {
errData = &errortypes.ErrorData{
Error: "journal_missing_index",
Message: "Journal missing index",
}
return
}
index, e := jrnlKindGen.GetKind(db, input.Key)
if e != nil {
err = e
return
}
input.Index = index
}
}
s.Journal = data
return
}
func (s *Spec) Refresh(db *database.Database) (
errData *errortypes.ErrorData, err error) {
errData, err = s.Parse(db, nil)
if err != nil || errData != nil {
return
}
err = s.CommitData(db)
if err != nil {
return
}
return
}
func (s *Spec) Parse(db *database.Database,
jrnlKindGen journal.KindGenerator) (
errData *errortypes.ErrorData, err error) {
hash := sha1.New()
hash.Write([]byte(filterSpecHash(s.Data)))
hashBytes := hash.Sum(nil)
s.Hash = fmt.Sprintf("%x", hashBytes)
resourcesSpec, err := s.ExtractResources()
if err != nil {
return
}
if resourcesSpec == "" {
errData = &errortypes.ErrorData{
Error: "unit_resources_block_missing",
Message: "Unit missing yaml resources block",
}
return
}
baseDecode := yaml.NewDecoder(strings.NewReader(resourcesSpec))
decoder := yaml.NewDecoder(strings.NewReader(resourcesSpec))
for {
baseDoc := &Base{}
err = baseDecode.Decode(baseDoc)
if err != nil {
if err == io.EOF {
err = nil
break
}
err = &errortypes.ParseError{
errors.Wrap(err, "spec: Failed to decode yaml doc"),
}
return
}
switch baseDoc.Kind {
case finder.InstanceKind, finder.ImageKind:
instYaml := &InstanceYaml{}
err = decoder.Decode(instYaml)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err,
"spec: Failed to decode instance yaml doc"),
}
return
}
errData, err = s.parseInstance(db, s.Organization, instYaml)
if err != nil || errData != nil {
return
}
case finder.FirewallKind:
fireYaml := &FirewallYaml{}
err = decoder.Decode(fireYaml)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err,
"spec: Failed to decode firewall yaml doc"),
}
return
}
errData, err = s.parseFirewall(db, s.Organization, fireYaml)
if err != nil || errData != nil {
return
}
case finder.DomainKind:
domnYaml := &DomainYaml{}
err = decoder.Decode(domnYaml)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err,
"spec: Failed to decode domain yaml doc"),
}
return
}
errData, err = s.parseDomain(db, s.Organization, domnYaml)
if err != nil || errData != nil {
return
}
case finder.JournalKind:
jrnlYaml := &JournalYaml{}
err = decoder.Decode(jrnlYaml)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err,
"spec: Failed to decode domain yaml doc"),
}
return
}
errData, err = s.parseJournal(db, jrnlKindGen, jrnlYaml)
if err != nil || errData != nil {
return
}
default:
errData = &errortypes.ErrorData{
Error: "unit_kind_invalid",
Message: "Unit kind is invalid",
}
return
}
}
return
}
func (s *Spec) CanMigrate(db *database.Database,
deply *deployment.Deployment, spc *Spec) (
errData *errortypes.ErrorData, err error) {
var inst *instance.Instance
if !deply.Instance.IsZero() {
inst, err = instance.Get(db, deply.Instance)
if err != nil {
return
}
}
if !settings.System.NoMigrateRefresh {
errData, err = s.Parse(db, nil)
if err != nil || errData != nil {
return
}
}
errData, err = spc.Parse(db, nil)
if err != nil || errData != nil {
return
}
if s.Pod != spc.Pod || s.Unit != spc.Unit {
err = &errortypes.ParseError{
errors.Newf("spec: Invalid unit"),
}
return
}
if s.Kind != spc.Kind {
errData = &errortypes.ErrorData{
Error: "unit_kind_conflict",
Message: "Cannot migrate to different kind",
}
return
}
if s.Instance == nil || spc.Instance == nil {
err = &errortypes.ParseError{
errors.Newf("spec: Instance not found"),
}
return
}
if s.Instance.Datacenter != spc.Instance.Datacenter {
errData = &errortypes.ErrorData{
Error: "instance_datacenter_conflict",
Message: "Cannot migrate to different instance datacenter",
}
return
}
if s.Instance.Zone != spc.Instance.Zone {
errData = &errortypes.ErrorData{
Error: "instance_zone_conflict",
Message: "Cannot migrate to different instance zone",
}
return
}
if s.Instance.Node != spc.Instance.Node &&
!spc.Instance.Node.IsZero() &&
inst.Node != spc.Instance.Node {
errData = &errortypes.ErrorData{
Error: "instance_node_coflict",
Message: "Cannot migrate to different instance node",
}
return
}
if s.Instance.Subnet != spc.Instance.Subnet {
errData = &errortypes.ErrorData{
Error: "instance_subnet_coflict",
Message: "Cannot migrate to different instance subnet",
}
return
}
curMountPaths := set.NewSet()
for _, mnt := range s.Instance.Mounts {
if mnt.Type == HostPath {
continue
}
curMountPaths.Add(mnt.Path)
}
newMountPaths := set.NewSet()
for _, mnt := range spc.Instance.Mounts {
if mnt.Type == HostPath {
continue
}
newMountPaths.Add(mnt.Path)
}
if !curMountPaths.IsEqual(newMountPaths) {
errData = &errortypes.ErrorData{
Error: "instance_mount_coflict",
Message: "Cannot migrate to different instance mounts",
}
return
}
return
}
func (s *Spec) Commit(db *database.Database) (err error) {
coll := db.Specs()
err = coll.Commit(s.Id, s)
if err != nil {
return
}
return
}
func (s *Spec) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Specs()
err = coll.CommitFields(s.Id, s, fields)
if err != nil {
return
}
return
}
func (s *Spec) CommitData(db *database.Database) (err error) {
coll := db.Specs()
err = coll.CommitFields(s.Id, s, set.NewSet(
"name", "count", "data", "instance", "firewall", "domain"))
if err != nil {
return
}
return
}
func (s *Spec) Insert(db *database.Database) (err error) {
coll := db.Specs()
resp, err := coll.InsertOne(db, s)
if err != nil {
err = database.ParseError(err)
return
}
s.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: spec/utils.go
================================================
package spec
import (
"regexp"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
type Named struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Unit bson.ObjectID `bson:"unit" json:"unit"`
Index int `bson:"index" json:"index"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
}
var (
yamlBlockRe = regexp.MustCompile("(?s)^```yaml\\n(.*?)```")
)
func filterSpecHash(input string) string {
return yamlBlockRe.ReplaceAllStringFunc(input, func(block string) string {
lines := strings.Split(block, "\n")
result := []string{}
for _, line := range lines {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "name:") ||
strings.HasPrefix(line, "count:") {
continue
}
result = append(result, line)
}
return strings.Join(result, "\n")
})
}
func New(podId, unitId, orgId bson.ObjectID, data string) (spc *Spec) {
spc = &Spec{
Id: bson.NewObjectID(),
Unit: unitId,
Pod: podId,
Organization: orgId,
Data: data,
}
return
}
func Get(db *database.Database, commitId bson.ObjectID) (
spc *Spec, err error) {
coll := db.Specs()
spc = &Spec{}
err = coll.FindOneId(commitId, spc)
if err != nil {
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (
spc *Spec, err error) {
coll := db.Specs()
spc = &Spec{}
err = coll.FindOne(db, query).Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (spcs []*Named, count int64, err error) {
coll := db.Specs()
spcs = []*Named{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetProjection(bson.M{
"_id": 1,
"unit": 1,
"index": 1,
"timestamp": 1,
}).
SetSort(bson.D{{"timestamp", -1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
spc := &Named{}
err = cursor.Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
spcs = append(spcs, spc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (
spcs []*Spec, err error) {
coll := db.Specs()
spcs = []*Spec{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
spc := &Spec{}
err = cursor.Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
spcs = append(spcs, spc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllIndexes(db *database.Database, query *bson.M) (
spcs []*Spec, err error) {
coll := db.Specs()
spcs = []*Spec{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetProjection(bson.M{
"_id": 1,
"unit": 1,
"index": 1,
"timestamp": 1,
}).
SetSort(bson.D{{"timestamp", 1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
spc := &Spec{}
err = cursor.Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
spcs = append(spcs, spc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllProjectSorted(db *database.Database, query *bson.M) (
spcs []*Spec, err error) {
coll := db.Specs()
spcs = []*Spec{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetProjection(bson.M{
"_id": 1,
"unit": 1,
"index": 1,
"timestamp": 1,
"hash": 1,
"data": 1,
}).
SetSort(bson.D{{"timestamp", -1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
spc := &Spec{}
err = cursor.Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
spcs = append(spcs, spc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllIds(db *database.Database) (specIds set.Set, err error) {
coll := db.Specs()
specIds = set.NewSet()
cursor, err := coll.Find(
db,
bson.M{},
options.Find().
SetProjection(bson.M{
"_id": 1,
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
spc := &Spec{}
err = cursor.Decode(spc)
if err != nil {
err = database.ParseError(err)
return
}
specIds.Add(spc.Id)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, commitId bson.ObjectID) (err error) {
coll := db.Specs()
_, err = coll.DeleteOne(db, &bson.M{
"_id": commitId,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
return
}
func RemoveAll(db *database.Database, query *bson.M) (err error) {
coll := db.Specs()
_, err = coll.DeleteMany(db, query)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: state/arps.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/arp"
"github.com/pritunl/pritunl-cloud/database"
)
var (
Arps = &ArpsState{}
ArpsPkg = NewPackage(Arps)
)
type ArpsState struct {
arpRecords map[string]set.Set
}
func (p *ArpsState) ArpRecords(namespace string) set.Set {
return p.arpRecords[namespace]
}
func (p *ArpsState) Refresh(pkg *Package,
db *database.Database) (err error) {
p.arpRecords = arp.BuildState(Instances.Instances(),
Vpcs.VpcsMap(), Vpcs.VpcIpsMap())
return
}
func (p *ArpsState) Apply(st *State) {
st.ArpRecords = p.ArpRecords
}
func init() {
ArpsPkg.
After(Instances).
After(Vpcs)
}
================================================
FILE: state/authorities.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/database"
)
var (
Authorities = &AuthoritiesState{}
AuthoritiesPkg = NewPackage(Authorities)
)
type AuthoritiesState struct {
authoritiesMap map[string][]*authority.Authority
}
func (p *AuthoritiesState) GetInstaceAuthorities(
orgId bson.ObjectID, roles []string) []*authority.Authority {
authrSet := set.NewSet()
authrs := []*authority.Authority{}
for _, role := range roles {
for _, authr := range p.authoritiesMap[role] {
if authrSet.Contains(authr.Id) || authr.Organization != orgId {
continue
}
authrSet.Add(authr.Id)
authrs = append(authrs, authr)
}
}
return authrs
}
func (p *AuthoritiesState) Refresh(pkg *Package,
db *database.Database) (err error) {
_, rolesSet := InstancesPreload.GetRoles()
authorities := AuthoritiesPreload.Authorities()
preloadRolesSet := AuthoritiesPreload.RolesSet()
roles := rolesSet.Copy()
roles.Subtract(preloadRolesSet)
missRoles := []string{}
for roleInf := range roles.Iter() {
missRoles = append(missRoles, roleInf.(string))
}
if len(missRoles) > 0 {
missAuthorities, e := authority.GetMapRoles(db, &bson.M{
"roles": &bson.M{
"$in": missRoles,
},
})
if e != nil {
err = e
return
}
for role, authrs := range missAuthorities {
authorities[role] = authrs
}
}
p.authoritiesMap = authorities
return
}
func (p *AuthoritiesState) Apply(st *State) {
st.GetInstaceAuthorities = p.GetInstaceAuthorities
}
func init() {
AuthoritiesPkg.
After(AuthoritiesPreload).
After(InstancesPreload)
}
================================================
FILE: state/authorities_preload.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/database"
)
var (
AuthoritiesPreload = &AuthoritiesPreloadState{}
AuthoritiesPreloadPkg = NewPackage(AuthoritiesPreload)
)
type AuthoritiesPreloadState struct {
authoritiesMap map[string][]*authority.Authority
authoritiesRolesSet set.Set
}
func (p *AuthoritiesPreloadState) Authorities() map[string][]*authority.Authority {
return p.authoritiesMap
}
func (p *AuthoritiesPreloadState) RolesSet() set.Set {
return p.authoritiesRolesSet
}
func (p *AuthoritiesPreloadState) Refresh(pkg *Package,
db *database.Database) (err error) {
roles, rolesSet := InstancesPreload.GetRoles()
if len(roles) == 0 {
p.authoritiesMap = map[string][]*authority.Authority{}
p.authoritiesRolesSet = set.NewSet()
return
}
authrsMap, err := authority.GetMapRoles(db, &bson.M{
"roles": &bson.M{
"$in": roles,
},
})
if err != nil {
return
}
p.authoritiesMap = authrsMap
p.authoritiesRolesSet = rolesSet
return
}
func (p *AuthoritiesPreloadState) Apply(st *State) {
}
================================================
FILE: state/datacenter.go
================================================
package state
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/node"
)
var (
Datacenter = &DatacenterState{}
DatacenterPkg = NewPackage(Datacenter)
)
type DatacenterState struct {
nodeDatacenter *datacenter.Datacenter
}
func (p *DatacenterState) NodeDatacenter() *datacenter.Datacenter {
return p.nodeDatacenter
}
func (p *DatacenterState) Refresh(pkg *Package,
db *database.Database) (err error) {
dcId := node.Self.Datacenter
if dcId.IsZero() {
p.nodeDatacenter = nil
pkg.Evict()
return
}
dc, e := datacenter.Get(db, dcId)
if e != nil {
err = e
return
}
p.nodeDatacenter = dc
pkg.Cache(15 * time.Second)
return
}
func (p *DatacenterState) Apply(st *State) {
st.NodeDatacenter = p.NodeDatacenter
}
================================================
FILE: state/deployments.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
)
var (
Deployments = &DeploymentsState{}
DeploymentsPkg = NewPackage(Deployments)
)
type DeploymentsResult struct {
DeploymentIds []bson.ObjectID `bson:"deployment_ids"`
PodIds []bson.ObjectID `bson:"pod_ids"`
UnitIds []bson.ObjectID `bson:"unit_ids"`
SpecIds []bson.ObjectID `bson:"spec_ids"`
Deployments []*deployment.Deployment `bson:"deployments"`
Pods []*pod.Pod `bson:"pods"`
Units []*unit.Unit `bson:"units"`
Specs []*spec.Spec `bson:"specs"`
SpecPodIds []bson.ObjectID `bson:"spec_pod_ids"`
SpecUnitIds []bson.ObjectID `bson:"spec_unit_ids"`
SpecSecretIds []bson.ObjectID `bson:"spec_secret_ids"`
SpecCertIds []bson.ObjectID `bson:"spec_cert_ids"`
SpecDomainIds []bson.ObjectID `bson:"spec_domain_ids"`
SpecIdUnits []*unit.Unit `bson:"spec_id_units"`
SpecPodUnits []*unit.Unit `bson:"spec_pod_units"`
SpecPods []*pod.Pod `bson:"spec_pods"`
SpecSecrets []*secret.Secret `bson:"spec_secrets"`
SpecCerts []*certificate.Certificate `bson:"spec_certs"`
SpecDomains []*domain.Domain `bson:"spec_domains"`
SpecDomainsRecords []*domain.Record `bson:"spec_domains_records"`
LinkedDeployments []*deployment.Deployment `bson:"linked_deployments"`
}
type DeploymentsState struct {
podsMap map[bson.ObjectID]*pod.Pod
unitsMap map[bson.ObjectID]*unit.Unit
specsMap map[bson.ObjectID]*spec.Spec
specsPodsMap map[bson.ObjectID]*pod.Pod
specsPodUnitsMap map[bson.ObjectID][]*unit.Unit
specsUnitsMap map[bson.ObjectID]*unit.Unit
specsDeploymentsMap map[bson.ObjectID]*deployment.Deployment
specsDomainsMap map[bson.ObjectID]*domain.Domain
specsSecretsMap map[bson.ObjectID]*secret.Secret
specsCertsMap map[bson.ObjectID]*certificate.Certificate
deploymentsNode map[bson.ObjectID]*deployment.Deployment
deploymentsReservedMap map[bson.ObjectID]*deployment.Deployment
deploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment
deploymentsInactiveMap map[bson.ObjectID]*deployment.Deployment
}
func (p *DeploymentsState) Pod(pdId bson.ObjectID) *pod.Pod {
return p.podsMap[pdId]
}
func (p *DeploymentsState) PodsMap() map[bson.ObjectID]*pod.Pod {
return p.podsMap
}
func (p *DeploymentsState) Unit(untId bson.ObjectID) *unit.Unit {
return p.unitsMap[untId]
}
func (p *DeploymentsState) UnitsMap() map[bson.ObjectID]*unit.Unit {
return p.unitsMap
}
func (p *DeploymentsState) Spec(commitId bson.ObjectID) *spec.Spec {
return p.specsMap[commitId]
}
func (p *DeploymentsState) SpecsMap() map[bson.ObjectID]*spec.Spec {
return p.specsMap
}
func (p *DeploymentsState) SpecPod(pdId bson.ObjectID) *pod.Pod {
return p.specsPodsMap[pdId]
}
func (p *DeploymentsState) SpecPodUnits(pdId bson.ObjectID) []*unit.Unit {
return p.specsPodUnitsMap[pdId]
}
func (p *DeploymentsState) SpecUnit(unitId bson.ObjectID) *unit.Unit {
return p.specsUnitsMap[unitId]
}
func (p *DeploymentsState) SpecsUnitsMap() map[bson.ObjectID]*unit.Unit {
return p.specsUnitsMap
}
func (p *DeploymentsState) SpecDomain(domnId bson.ObjectID) *domain.Domain {
return p.specsDomainsMap[domnId]
}
func (p *DeploymentsState) SpecSecret(secrID bson.ObjectID) *secret.Secret {
return p.specsSecretsMap[secrID]
}
func (p *DeploymentsState) SpecCert(
certId bson.ObjectID) *certificate.Certificate {
return p.specsCertsMap[certId]
}
func (p *DeploymentsState) SpecCertMap() map[bson.ObjectID]*certificate.Certificate {
return p.specsCertsMap
}
func (p *DeploymentsState) DeploymentsNode() map[bson.ObjectID]*deployment.Deployment {
return p.deploymentsNode
}
func (p *DeploymentsState) DeploymentReserved(deplyId bson.ObjectID) *deployment.Deployment {
return p.deploymentsReservedMap[deplyId]
}
func (p *DeploymentsState) DeploymentsReserved() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = p.deploymentsReservedMap
return
}
func (p *DeploymentsState) DeploymentDeployed(deplyId bson.ObjectID) *deployment.Deployment {
return p.deploymentsDeployedMap[deplyId]
}
func (p *DeploymentsState) DeploymentsDeployed() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = p.deploymentsDeployedMap
return
}
func (p *DeploymentsState) DeploymentsDestroy() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = p.deploymentsInactiveMap
return
}
func (p *DeploymentsState) DeploymentInactive(deplyId bson.ObjectID) *deployment.Deployment {
return p.deploymentsInactiveMap[deplyId]
}
func (p *DeploymentsState) DeploymentsInactive() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = p.deploymentsInactiveMap
return
}
func (p *DeploymentsState) Deployment(deplyId bson.ObjectID) (
deply *deployment.Deployment) {
deply = p.deploymentsDeployedMap[deplyId]
if deply != nil {
return
}
deply = p.deploymentsReservedMap[deplyId]
if deply != nil {
return
}
deply = p.deploymentsInactiveMap[deplyId]
if deply != nil {
return
}
return
}
func (p *DeploymentsState) Refresh(pkg *Package, db *database.Database) (err error) {
pipeline := bson.A{
bson.M{
"$match": bson.M{
"node": node.Self.Id,
},
},
bson.M{
"$group": bson.M{
"_id": nil,
"deployments": bson.M{"$push": "$$ROOT"},
"deployment_ids": bson.M{"$addToSet": "$_id"},
"pod_ids": bson.M{"$addToSet": "$pod"},
"unit_ids": bson.M{"$addToSet": "$unit"},
"spec_ids": bson.M{"$addToSet": "$spec"},
},
},
bson.M{
"$lookup": bson.M{
"from": "specs",
"localField": "spec_ids",
"foreignField": "_id",
"as": "specs",
},
},
bson.M{
"$lookup": bson.M{
"from": "units",
"localField": "unit_ids",
"foreignField": "_id",
"as": "units",
},
},
bson.M{
"$lookup": bson.M{
"from": "pods",
"localField": "pod_ids",
"foreignField": "_id",
"as": "pods",
},
},
bson.M{
"$addFields": bson.M{
"specs_data": bson.M{
"$reduce": bson.M{
"input": "$specs",
"initialValue": bson.M{
"pod_ids": bson.A{},
"secret_ids": bson.A{},
"cert_ids": bson.A{},
"domain_ids": bson.A{},
"unit_ids": bson.A{},
},
"in": bson.M{
"pod_ids": bson.M{
"$concatArrays": bson.A{
"$$value.pod_ids",
bson.M{
"$ifNull": bson.A{
"$$this.instance.pods",
bson.A{},
},
},
},
},
"secret_ids": bson.M{
"$concatArrays": bson.A{
"$$value.secret_ids",
bson.M{
"$ifNull": bson.A{
"$$this.instance.secrets",
bson.A{},
},
},
},
},
"cert_ids": bson.M{
"$concatArrays": bson.A{
"$$value.cert_ids",
bson.M{
"$ifNull": bson.A{
"$$this.instance.certificates",
bson.A{},
},
},
},
},
"domain_ids": bson.M{
"$concatArrays": bson.A{
"$$value.domain_ids",
bson.M{
"$map": bson.M{
"input": bson.M{
"$ifNull": bson.A{
"$$this.domain.records",
bson.A{},
},
},
"as": "record",
"in": "$$record.domain",
},
},
},
},
"unit_ids": bson.M{
"$concatArrays": bson.A{
"$$value.unit_ids",
bson.M{
"$reduce": bson.M{
"input": bson.M{
"$ifNull": bson.A{
"$$this.firewall.ingress",
bson.A{},
},
},
"initialValue": bson.A{},
"in": bson.M{
"$concatArrays": bson.A{
"$$value",
bson.M{
"$ifNull": bson.A{
bson.M{
"$map": bson.M{
"input": bson.M{
"$ifNull": bson.A{
"$$this.sources",
bson.A{},
},
},
"as": "source",
"in": "$$source.id",
},
},
bson.A{},
},
},
},
},
},
},
},
},
},
},
},
},
},
bson.M{
"$addFields": bson.M{
"spec_pod_ids": "$specs_data.pod_ids",
"spec_secret_ids": "$specs_data.secret_ids",
"spec_cert_ids": "$specs_data.cert_ids",
"spec_domain_ids": "$specs_data.domain_ids",
"spec_unit_ids": "$specs_data.unit_ids",
},
},
bson.M{
"$lookup": bson.M{
"from": "units",
"localField": "spec_unit_ids",
"foreignField": "_id",
"as": "spec_id_units",
},
},
bson.M{
"$lookup": bson.M{
"from": "units",
"localField": "spec_pod_ids",
"foreignField": "pod",
"as": "spec_pod_units",
},
},
bson.M{
"$lookup": bson.M{
"from": "pods",
"localField": "spec_pod_ids",
"foreignField": "_id",
"as": "spec_pods",
},
},
bson.M{
"$lookup": bson.M{
"from": "secrets",
"localField": "spec_secret_ids",
"foreignField": "_id",
"as": "spec_secrets",
},
},
bson.M{
"$lookup": bson.M{
"from": "certificates",
"localField": "spec_cert_ids",
"foreignField": "_id",
"as": "spec_certs",
},
},
bson.M{
"$lookup": bson.M{
"from": "domains",
"localField": "spec_domain_ids",
"foreignField": "_id",
"as": "spec_domains",
},
},
bson.M{
"$lookup": bson.M{
"from": "domains_records",
"localField": "spec_domain_ids",
"foreignField": "domain",
"as": "spec_domains_records",
},
},
bson.M{
"$addFields": bson.M{
"spec_deployment_id_ids": bson.M{
"$reduce": bson.M{
"input": "$spec_id_units",
"initialValue": bson.A{},
"in": bson.M{
"$concatArrays": bson.A{
"$$value",
bson.M{
"$ifNull": bson.A{
"$$this.deployments",
bson.A{},
},
},
},
},
},
},
"spec_deployment_pod_ids": bson.M{
"$reduce": bson.M{
"input": "$spec_pod_units",
"initialValue": bson.A{},
"in": bson.M{
"$concatArrays": bson.A{
"$$value",
bson.M{
"$ifNull": bson.A{
"$$this.deployments",
bson.A{},
},
},
},
},
},
},
"pod_deployment_ids": bson.M{
"$reduce": bson.M{
"input": "$units",
"initialValue": bson.A{},
"in": bson.M{
"$setUnion": bson.A{
"$$value",
bson.M{
"$ifNull": bson.A{
"$$this.deployments",
bson.A{},
},
},
},
},
},
},
},
},
bson.M{
"$addFields": bson.M{
"linked_deployment_ids": bson.M{
"$setDifference": bson.A{
bson.M{
"$setUnion": bson.A{
"$spec_deployment_id_ids",
"$spec_deployment_pod_ids",
"$pod_deployment_ids",
},
},
"$deployment_ids",
},
},
},
},
bson.M{
"$lookup": bson.M{
"from": "deployments",
"localField": "linked_deployment_ids",
"foreignField": "_id",
"as": "linked_deployments",
},
},
bson.M{
"$project": bson.M{
"deployment_ids": 1,
"pod_ids": 1,
"unit_ids": 1,
"spec_ids": 1,
"deployments": 1,
"pods": 1,
"units": 1,
"specs": 1,
"spec_pod_ids": 1,
"spec_unit_ids": 1,
"spec_secret_ids": 1,
"spec_cert_ids": 1,
"spec_domain_ids": 1,
"spec_id_units": 1,
"spec_pod_units": 1,
"spec_pods": 1,
"spec_secrets": 1,
"spec_certs": 1,
"spec_domains": 1,
"spec_domains_records": 1,
"linked_deployments": 1,
},
},
}
cursor, err := db.Deployments().Aggregate(db, pipeline)
if err != nil {
return
}
defer cursor.Close(db)
result := &DeploymentsResult{}
if cursor.Next(db) {
err = cursor.Decode(result)
if err != nil {
return
}
}
deploymentsNode := map[bson.ObjectID]*deployment.Deployment{}
deploymentsReservedMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsInactiveMap := map[bson.ObjectID]*deployment.Deployment{}
for _, deply := range result.Deployments {
deploymentsNode[deply.Id] = deply
switch deply.State {
case deployment.Reserved:
deploymentsReservedMap[deply.Id] = deply
case deployment.Deployed:
switch deply.Action {
case deployment.Destroy, deployment.Archive, deployment.Restore:
deploymentsInactiveMap[deply.Id] = deply
default:
deploymentsDeployedMap[deply.Id] = deply
}
case deployment.Archived:
deploymentsInactiveMap[deply.Id] = deply
}
}
p.deploymentsNode = deploymentsNode
specsMap := map[bson.ObjectID]*spec.Spec{}
for _, spec := range result.Specs {
specsMap[spec.Id] = spec
}
p.specsMap = specsMap
specsCertsMap := map[bson.ObjectID]*certificate.Certificate{}
for _, specCert := range result.SpecCerts {
specsCertsMap[specCert.Id] = specCert
}
p.specsCertsMap = specsCertsMap
specsSecretsMap := map[bson.ObjectID]*secret.Secret{}
for _, specSecret := range result.SpecSecrets {
specsSecretsMap[specSecret.Id] = specSecret
}
p.specsSecretsMap = specsSecretsMap
specsPodsMap := map[bson.ObjectID]*pod.Pod{}
for _, specPod := range result.SpecPods {
specsPodsMap[specPod.Id] = specPod
}
p.specsPodsMap = specsPodsMap
specDomains := domain.PreloadedRecords(
result.SpecDomains, result.SpecDomainsRecords)
specsDomainsMap := map[bson.ObjectID]*domain.Domain{}
for _, specDomain := range specDomains {
specsDomainsMap[specDomain.Id] = specDomain
}
p.specsDomainsMap = specsDomainsMap
specUnitsIds := set.NewSet()
specsUnitsMap := map[bson.ObjectID]*unit.Unit{}
specsPodUnitsMap := map[bson.ObjectID][]*unit.Unit{}
for _, specUnit := range result.SpecIdUnits {
if specUnitsIds.Contains(specUnit.Id) {
continue
}
specUnitsIds.Add(specUnit.Id)
specsUnitsMap[specUnit.Id] = specUnit
specsPodUnitsMap[specUnit.Pod] = append(
specsPodUnitsMap[specUnit.Pod], specUnit)
}
for _, specUnit := range result.SpecPodUnits {
if specUnitsIds.Contains(specUnit.Id) {
continue
}
specUnitsIds.Add(specUnit.Id)
specsUnitsMap[specUnit.Id] = specUnit
specsPodUnitsMap[specUnit.Pod] = append(
specsPodUnitsMap[specUnit.Pod], specUnit)
}
p.specsUnitsMap = specsUnitsMap
p.specsPodUnitsMap = specsPodUnitsMap
for _, deply := range result.LinkedDeployments {
switch deply.State {
case deployment.Reserved:
deploymentsReservedMap[deply.Id] = deply
case deployment.Deployed:
switch deply.Action {
case deployment.Destroy, deployment.Archive, deployment.Restore:
deploymentsInactiveMap[deply.Id] = deply
default:
deploymentsDeployedMap[deply.Id] = deply
}
case deployment.Archived:
deploymentsInactiveMap[deply.Id] = deply
}
}
p.deploymentsReservedMap = deploymentsReservedMap
p.deploymentsDeployedMap = deploymentsDeployedMap
p.deploymentsInactiveMap = deploymentsInactiveMap
podsMap := map[bson.ObjectID]*pod.Pod{}
for _, pd := range result.Pods {
podsMap[pd.Id] = pd
}
p.podsMap = podsMap
unitsMap := map[bson.ObjectID]*unit.Unit{}
for _, unt := range result.Units {
unitsMap[unt.Id] = unt
}
p.unitsMap = unitsMap
return
}
func (p *DeploymentsState) Apply(st *State) {
st.Pod = p.Pod
st.PodsMap = p.PodsMap
st.Unit = p.Unit
st.UnitsMap = p.UnitsMap
st.Spec = p.Spec
st.SpecPod = p.SpecPod
st.SpecPodUnits = p.SpecPodUnits
st.SpecUnit = p.SpecUnit
st.SpecsUnitsMap = p.SpecsUnitsMap
st.SpecDomain = p.SpecDomain
st.SpecSecret = p.SpecSecret
st.SpecCert = p.SpecCert
st.DeploymentsNode = p.DeploymentsNode
st.DeploymentReserved = p.DeploymentReserved
st.DeploymentsReserved = p.DeploymentsReserved
st.DeploymentDeployed = p.DeploymentDeployed
st.DeploymentsDeployed = p.DeploymentsDeployed
st.DeploymentsDestroy = p.DeploymentsDestroy
st.DeploymentInactive = p.DeploymentInactive
st.DeploymentsInactive = p.DeploymentsInactive
st.Deployment = p.Deployment
}
================================================
FILE: state/disks.go
================================================
package state
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/node"
)
var (
Disks = &DisksState{}
DisksPkg = NewPackage(Disks)
)
type DisksState struct {
disks []*disk.Disk
instanceDisks map[bson.ObjectID][]*disk.Disk
deploymentDisks map[bson.ObjectID][]*disk.Disk
}
func (p *DisksState) Disks() []*disk.Disk {
return p.disks
}
func (p *DisksState) GetInstaceDisks(instId bson.ObjectID) []*disk.Disk {
return p.instanceDisks[instId]
}
func (p *DisksState) GetDeploymentDisks(
deplyId bson.ObjectID) []*disk.Disk {
return p.deploymentDisks[deplyId]
}
func (p *DisksState) InstaceDisksMap() map[bson.ObjectID][]*disk.Disk {
return p.instanceDisks
}
func (p *DisksState) Refresh(pkg *Package,
db *database.Database) (err error) {
ndeId := node.Self.Id
ndePools := node.Self.Pools
disks, err := disk.GetNode(db, ndeId, ndePools)
if err != nil {
return
}
p.disks = disks
instanceDisks := map[bson.ObjectID][]*disk.Disk{}
deploymentDisks := map[bson.ObjectID][]*disk.Disk{}
for _, dsk := range disks {
if !dsk.Instance.IsZero() {
instanceDisks[dsk.Instance] = append(
instanceDisks[dsk.Instance], dsk)
}
if !dsk.Deployment.IsZero() {
deploymentDisks[dsk.Deployment] = append(
deploymentDisks[dsk.Deployment], dsk)
}
}
p.instanceDisks = instanceDisks
p.deploymentDisks = deploymentDisks
return
}
func (p *DisksState) Apply(st *State) {
st.Disks = p.Disks
st.GetInstaceDisks = p.GetInstaceDisks
st.GetDeploymentDisks = p.GetDeploymentDisks
st.InstaceDisksMap = p.InstaceDisksMap
}
================================================
FILE: state/domains.go
================================================
package state
import (
"net"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/imds/types"
)
var (
Domains = &DomainsState{}
DomainsPkg = NewPackage(Domains)
)
type DomainsState struct {
domains map[bson.ObjectID][]*types.Domain
}
func (p *DomainsState) GetDomains(orgId bson.ObjectID) []*types.Domain {
return p.domains[orgId]
}
func (p *DomainsState) Refresh(pkg *Package,
db *database.Database) (err error) {
coll := db.Domains()
rootDomains := map[bson.ObjectID]*domain.Domain{}
records := map[bson.ObjectID][]*types.Domain{}
cursor, err := coll.Find(db, bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
dmn := &domain.Domain{}
err = cursor.Decode(dmn)
if err != nil {
err = database.ParseError(err)
return
}
rootDomains[dmn.Id] = dmn
}
coll = db.DomainsRecords()
cursor, err = coll.Find(
db,
bson.M{},
options.Find().SetSort(bson.D{{"_id", 1}}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
rec := &domain.Record{}
err = cursor.Decode(rec)
if err != nil {
err = database.ParseError(err)
return
}
if rec.IsDeleted() {
continue
}
dmn := rootDomains[rec.Domain]
if dmn == nil {
continue
}
dmnRec := &types.Domain{
Domain: rec.SubDomain + "." + dmn.RootDomain + ".",
Type: rec.Type,
}
switch rec.Type {
case domain.A:
dmnRec.Ip = net.ParseIP(rec.Value)
case domain.AAAA:
dmnRec.Ip = net.ParseIP(rec.Value)
case domain.CNAME:
dmnRec.Target = rec.Value + "."
default:
continue
}
records[dmn.Organization] = append(records[dmn.Organization], dmnRec)
}
p.domains = records
return
}
func (p *DomainsState) Apply(st *State) {
st.GetDomains = p.GetDomains
}
================================================
FILE: state/firewalls.go
================================================
package state
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/node"
)
var (
Firewalls = &FirewallsState{}
FirewallsPkg = NewPackage(Firewalls)
)
type FirewallsState struct {
nodeFirewall []*firewall.Rule
firewalls map[string][]*firewall.Rule
firewallMaps map[string][]*firewall.Mapping
instanceNamespaces map[bson.ObjectID][]string
}
func (p *FirewallsState) NodeFirewall() []*firewall.Rule {
return p.nodeFirewall
}
func (p *FirewallsState) Firewalls() map[string][]*firewall.Rule {
return p.firewalls
}
func (p *FirewallsState) FirewallMaps() map[string][]*firewall.Mapping {
return p.firewallMaps
}
func (p *FirewallsState) GetInstanceNamespaces(
instId bson.ObjectID) []string {
return p.instanceNamespaces[instId]
}
func (p *FirewallsState) Refresh(pkg *Package,
db *database.Database) (err error) {
specRules, err := firewall.GetSpecRules(Instances.Instances(),
Deployments.DeploymentsNode(), Deployments.SpecsMap(),
Deployments.SpecsUnitsMap(), Deployments.DeploymentsDeployed())
if err != nil {
return
}
_, rolesSet := InstancesPreload.GetRoles()
firesMap := FirewallsPreload.Firewalls()
firewallRolesSet := FirewallsPreload.RolesSet()
roles := rolesSet.Copy()
roles.Subtract(firewallRolesSet)
missRoles := []string{}
for roleInf := range roles.Iter() {
missRoles = append(missRoles, roleInf.(string))
}
if len(missRoles) > 0 {
missFiresMap, e := firewall.GetMapRoles(db, missRoles)
if e != nil {
err = e
return
}
for role, fires := range missFiresMap {
firesMap[role] = fires
}
}
nodeFirewall, firewalls, firewallMaps, instNamespaces, err :=
firewall.GetAllIngressPreloaded(node.Self, Instances.Instances(),
specRules, Instances.NodePortsMap(), firesMap)
if err != nil {
return
}
p.nodeFirewall = nodeFirewall
p.firewalls = firewalls
p.firewallMaps = firewallMaps
p.instanceNamespaces = instNamespaces
return
}
func (p *FirewallsState) Apply(st *State) {
st.NodeFirewall = p.NodeFirewall
st.Firewalls = p.Firewalls
st.FirewallMaps = p.FirewallMaps
st.GetInstanceNamespaces = p.GetInstanceNamespaces
}
func init() {
FirewallsPkg.
After(FirewallsPreload).
After(Instances).
After(Vpcs).
After(Deployments)
}
================================================
FILE: state/firewalls_preload.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/firewall"
)
var (
FirewallsPreload = &FirewallsPreloadState{}
FirewallsPreloadPkg = NewPackage(FirewallsPreload)
)
type FirewallsPreloadState struct {
firewalls map[string][]*firewall.Firewall
firewallsRolesSet set.Set
}
func (p *FirewallsPreloadState) Firewalls() map[string][]*firewall.Firewall {
return p.firewalls
}
func (p *FirewallsPreloadState) RolesSet() set.Set {
return p.firewallsRolesSet
}
func (p *FirewallsPreloadState) Refresh(pkg *Package,
db *database.Database) (err error) {
roles, rolesSet := InstancesPreload.GetRoles()
if len(roles) == 0 {
p.firewalls = map[string][]*firewall.Firewall{}
p.firewallsRolesSet = set.NewSet()
return
}
firesMap, err := firewall.GetMapRoles(db, roles)
if err != nil {
return
}
p.firewalls = firesMap
p.firewallsRolesSet = rolesSet
return
}
func (p *FirewallsPreloadState) Apply(st *State) {
}
================================================
FILE: state/instances.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/nodeport"
)
var (
Instances = &InstancesState{}
InstancesPkg = NewPackage(Instances)
)
type InstancesState struct {
instances []*instance.Instance
instancesMap map[bson.ObjectID]*instance.Instance
nodePortsMap map[string][]*nodeport.Mapping
}
func (p *InstancesState) GetInstace(
instId bson.ObjectID) *instance.Instance {
if instId.IsZero() {
return nil
}
return p.instancesMap[instId]
}
func (p *InstancesState) Instances() []*instance.Instance {
return p.instances
}
func (p *InstancesState) NodePortsMap() map[string][]*nodeport.Mapping {
return p.nodePortsMap
}
func (p *InstancesState) Refresh(pkg *Package,
db *database.Database) (err error) {
instances := InstancesPreload.GetInstances()
instances = instance.LoadAllVirt(instances,
Pools.NodePools(), Disks.InstaceDisksMap())
p.instances = instances
instId := set.NewSet()
instancesMap := map[bson.ObjectID]*instance.Instance{}
nodePortsMap := map[string][]*nodeport.Mapping{}
for _, inst := range instances {
instId.Add(inst.Id)
instancesMap[inst.Id] = inst
nodePortsMap[inst.NetworkNamespace] = append(
nodePortsMap[inst.NetworkNamespace], inst.NodePorts...)
}
p.instancesMap = instancesMap
p.nodePortsMap = nodePortsMap
return
}
func (p *InstancesState) Apply(st *State) {
st.GetInstace = p.GetInstace
st.Instances = p.Instances
st.NodePortsMap = p.NodePortsMap
}
func init() {
InstancesPkg.
After(InstancesPreload).
After(Disks).
After(Pools)
}
================================================
FILE: state/instances_preload.go
================================================
package state
import (
"sync"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
)
var (
InstancesPreload = &InstancesPreloadState{}
InstancesPreloadPkg = NewPackage(InstancesPreload)
)
type InstancesPreloadState struct {
roles []string
rolesSet set.Set
rolesLock sync.Mutex
instances []*instance.Instance
}
func (p *InstancesPreloadState) GetInstances() []*instance.Instance {
return p.instances
}
func (p *InstancesPreloadState) GetRoles() (roles []string, rolesSet set.Set) {
p.rolesLock.Lock()
roles = p.roles
rolesSet = p.rolesSet
p.rolesLock.Unlock()
return
}
func (p *InstancesPreloadState) setRoles(roles []string, rolesSet set.Set) {
p.rolesLock.Lock()
p.roles = roles
p.rolesSet = rolesSet
p.rolesLock.Unlock()
return
}
func (p *InstancesPreloadState) Refresh(pkg *Package,
db *database.Database) (err error) {
ndeId := node.Self.Id
instances, rolesSet, err := instance.GetAllRoles(db, &bson.M{
"node": ndeId,
})
if err != nil {
return
}
p.instances = instances
nde := node.Self
if nde.Firewall {
roles := nde.Roles
for _, role := range roles {
rolesSet.Add(role)
}
}
roles := []string{}
for instRoleInf := range rolesSet.Iter() {
roles = append(roles, instRoleInf.(string))
}
p.setRoles(roles, rolesSet)
return
}
func (p *InstancesPreloadState) Apply(st *State) {
}
================================================
FILE: state/network.go
================================================
package state
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
Network = &NetworkState{}
NetworkPkg = NewPackage(Network)
)
type NetworkState struct {
namespaces []string
interfaces []string
interfacesSet set.Set
}
func (p *NetworkState) Namespaces() []string {
return p.namespaces
}
func (p *NetworkState) Interfaces() []string {
return p.interfaces
}
func (p *NetworkState) HasInterfaces(iface string) bool {
return p.interfacesSet.Contains(iface)
}
func (p *NetworkState) Refresh(pkg *Package,
db *database.Database) (err error) {
namespaces, err := utils.GetNamespaces()
if err != nil {
return
}
p.namespaces = namespaces
interfaces, interfacesSet, err := utils.GetInterfacesSet()
if err != nil {
return
}
p.interfaces = interfaces
p.interfacesSet = interfacesSet
return
}
func (p *NetworkState) Apply(st *State) {
st.Namespaces = p.Namespaces
st.Interfaces = p.Interfaces
st.HasInterfaces = p.HasInterfaces
}
================================================
FILE: state/package.go
================================================
package state
import (
"reflect"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/database"
)
var (
refCounter = 0
registry = map[PackageHandler]*Package{}
)
type PackageHandler interface {
Refresh(pkg *Package, db *database.Database) (err error)
Apply(st *State)
}
type Package struct {
name string
ref int
after set.Set
ttl time.Duration
handler PackageHandler
}
func (p *Package) Cache(d time.Duration) *Package {
p.ttl = d
return p
}
func (p *Package) Evict() *Package {
p.ttl = 0
return p
}
func (p *Package) After(handler PackageHandler) *Package {
p.after.Add(registry[handler].ref)
return p
}
func NewPackage(handler PackageHandler) *Package {
refCounter += 1
pkg := &Package{
name: reflect.TypeOf(handler).Elem().Name(),
ref: refCounter,
handler: handler,
after: set.NewSet(),
}
registry[handler] = pkg
return pkg
}
func RefreshAll(db *database.Database, runtimes *Runtimes) (err error) {
inDegree := make(map[int]int)
dependents := make(map[int][]*Package)
refToPackage := make(map[int]*Package)
for _, pkg := range registry {
inDegree[pkg.ref] = 0
dependents[pkg.ref] = []*Package{}
refToPackage[pkg.ref] = pkg
}
for _, pkg := range registry {
for afterRef := range pkg.after.Iter() {
afterRefInt := afterRef.(int)
inDegree[pkg.ref]++
dependents[afterRefInt] = append(dependents[afterRefInt], pkg)
}
}
ready := make(chan *Package, len(registry))
for ref, degree := range inDegree {
if degree == 0 {
ready <- refToPackage[ref]
}
}
done := make(chan *Package, len(registry))
errors := make(chan error, len(registry))
completed := make(map[int]bool)
processPackage := func(pkg *Package) {
go func() {
defer func() {
done <- pkg
}()
start := time.Now()
refreshErr := pkg.handler.Refresh(pkg, db)
dur := time.Since(start)
if refreshErr != nil {
errors <- refreshErr
}
runtimes.SetState(pkg.name, dur)
}()
}
processed := 0
totalPackages := len(registry)
for processed < totalPackages {
select {
case pkg := <-ready:
processPackage(pkg)
case completedPkg := <-done:
processed++
completed[completedPkg.ref] = true
for _, dependent := range dependents[completedPkg.ref] {
if !completed[dependent.ref] {
allDepsCompleted := true
for afterRef := range dependent.after.Iter() {
if !completed[afterRef.(int)] {
allDepsCompleted = false
}
}
if allDepsCompleted {
ready <- dependent
}
}
}
case refreshErr := <-errors:
if err == nil {
err = refreshErr
}
}
}
select {
case refreshErr := <-errors:
if err == nil {
err = refreshErr
}
default:
}
return
}
func ApplyAll(st *State) {
for _, pkg := range registry {
pkg.handler.Apply(st)
}
}
================================================
FILE: state/pools.go
================================================
package state
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/pool"
)
var (
Pools = &PoolsState{}
PoolsPkg = NewPackage(Pools)
)
type PoolsState struct {
nodePools []*pool.Pool
}
func (p *PoolsState) NodePools() []*pool.Pool {
return p.nodePools
}
func (p *PoolsState) Refresh(pkg *Package,
db *database.Database) (err error) {
zneId := node.Self.Zone
if zneId.IsZero() {
p.nodePools = nil
return
}
pools, err := pool.GetAll(db, &bson.M{
"zone": zneId,
})
if err != nil {
return
}
p.nodePools = pools
return
}
func (p *PoolsState) Apply(st *State) {
st.NodePools = p.NodePools
}
================================================
FILE: state/runtimes.go
================================================
package state
import (
"fmt"
"sync"
"time"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
type Runtimes struct {
State map[string]time.Duration
Network time.Duration
Ipset time.Duration
Iptables time.Duration
Disks time.Duration
Instances time.Duration
Namespaces time.Duration
Pods time.Duration
Deployments time.Duration
Imds time.Duration
Wait time.Duration
Total time.Duration
lock sync.Mutex
}
func (r *Runtimes) Init() {
r.State = map[string]time.Duration{}
}
func (r *Runtimes) SetState(key string, dur time.Duration) {
r.lock.Lock()
r.State[key] = dur
r.lock.Unlock()
}
func (r *Runtimes) Log() {
fields := logrus.Fields{
"network": fmt.Sprintf("%v", r.Network),
"ipset": fmt.Sprintf("%v", r.Ipset),
"iptables": fmt.Sprintf("%v", r.Iptables),
"disks": fmt.Sprintf("%v", r.Disks),
"namespaces": fmt.Sprintf("%v", r.Namespaces),
"pods": fmt.Sprintf("%v", r.Pods),
"deployments": fmt.Sprintf("%v", r.Deployments),
"imds": fmt.Sprintf("%v", r.Imds),
"wait": fmt.Sprintf("%v", r.Wait),
"total": fmt.Sprintf("%v", r.Total),
}
for key, dur := range r.State {
key = utils.ToSnakeCase(key)
fields[key] = fmt.Sprintf("%v", dur)
}
logrus.WithFields(fields).Warn("sync: High state sync runtime")
}
================================================
FILE: state/schedulers.go
================================================
package state
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/scheduler"
)
var (
Schedulers = &SchedulersState{}
SchedulersPkg = NewPackage(Schedulers)
)
type SchedulersState struct {
schedulers []*scheduler.Scheduler
}
func (p *SchedulersState) Schedulers() []*scheduler.Scheduler {
return p.schedulers
}
func (p *SchedulersState) Refresh(pkg *Package,
db *database.Database) (err error) {
schedulers, err := scheduler.GetAll(db)
if err != nil {
return
}
p.schedulers = schedulers
return
}
func (p *SchedulersState) Apply(st *State) {
st.Schedulers = p.Schedulers
}
================================================
FILE: state/state.go
================================================
package state
import (
"sync"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/scheduler"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
)
type State struct {
waiter *sync.WaitGroup
// Datacenter
NodeDatacenter func() *datacenter.Datacenter
// Zone
NodeZone func() *zone.Zone
// Zones
VxLan func() bool
GetZone func(zneId bson.ObjectID) *zone.Zone
Nodes func() []*node.Node
// Network
Namespaces func() []string
Interfaces func() []string
HasInterfaces func(iface string) bool
// Domains
GetDomains func(orgId bson.ObjectID) []*types.Domain
// Pools
NodePools func() []*pool.Pool
// Disks
Disks func() []*disk.Disk
GetInstaceDisks func(instId bson.ObjectID) []*disk.Disk
GetDeploymentDisks func(deplyId bson.ObjectID) []*disk.Disk
InstaceDisksMap func() map[bson.ObjectID][]*disk.Disk
// Vpcs
Vpc func(vpcId bson.ObjectID) *vpc.Vpc
VpcsMap func() map[bson.ObjectID]*vpc.Vpc
VpcIps func(vpcId bson.ObjectID) []*vpc.VpcIp
VpcIpsMap func() map[bson.ObjectID][]*vpc.VpcIp
Vpcs func() []*vpc.Vpc
// Deployments
Pod func(pdId bson.ObjectID) *pod.Pod
PodsMap func() map[bson.ObjectID]*pod.Pod
Unit func(unitId bson.ObjectID) *unit.Unit
UnitsMap func() map[bson.ObjectID]*unit.Unit
Spec func(commitId bson.ObjectID) *spec.Spec
SpecsMap func() map[bson.ObjectID]*spec.Spec
SpecPod func(pdId bson.ObjectID) *pod.Pod
SpecPodUnits func(pdId bson.ObjectID) []*unit.Unit
SpecUnit func(unitId bson.ObjectID) *unit.Unit
SpecsUnitsMap func() map[bson.ObjectID]*unit.Unit
SpecDomain func(domnId bson.ObjectID) *domain.Domain
SpecSecret func(secrID bson.ObjectID) *secret.Secret
SpecCert func(certId bson.ObjectID) *certificate.Certificate
DeploymentsNode func() map[bson.ObjectID]*deployment.Deployment
DeploymentReserved func(deplyId bson.ObjectID) *deployment.Deployment
DeploymentsReserved func() map[bson.ObjectID]*deployment.Deployment
DeploymentDeployed func(deplyId bson.ObjectID) *deployment.Deployment
DeploymentsDeployed func() map[bson.ObjectID]*deployment.Deployment
DeploymentsDestroy func() map[bson.ObjectID]*deployment.Deployment
DeploymentInactive func(deplyId bson.ObjectID) *deployment.Deployment
DeploymentsInactive func() map[bson.ObjectID]*deployment.Deployment
Deployment func(deplyId bson.ObjectID) *deployment.Deployment
// Instances
GetInstace func(instId bson.ObjectID) *instance.Instance
Instances func() []*instance.Instance
NodePortsMap func() map[string][]*nodeport.Mapping
GetInstaceAuthorities func(orgId bson.ObjectID,
roles []string) []*authority.Authority
// Virtuals
DiskInUse func(instId, dskId bson.ObjectID) bool
GetVirt func(instId bson.ObjectID) *vm.VirtualMachine
VirtsMap func() map[bson.ObjectID]*vm.VirtualMachine
// Schedulers
Schedulers func() []*scheduler.Scheduler
// Firewalls
NodeFirewall func() []*firewall.Rule
Firewalls func() map[string][]*firewall.Rule
FirewallMaps func() map[string][]*firewall.Mapping
ArpRecords func(namespace string) set.Set
GetInstanceNamespaces func(instId bson.ObjectID) []string
}
func (s *State) Node() *node.Node {
return node.Self
}
func (s *State) WaitAdd() {
s.waiter.Add(1)
}
func (s *State) WaitDone() {
s.waiter.Done()
}
func (s *State) Wait() {
s.waiter.Wait()
}
func GetState(runtimes *Runtimes) (stat *State, err error) {
db := database.GetDatabase()
defer db.Close()
err = RefreshAll(db, runtimes)
if err != nil {
return
}
stat = &State{
waiter: &sync.WaitGroup{},
}
ApplyAll(stat)
return
}
================================================
FILE: state/state_old.go
================================================
package state
import (
"io/ioutil"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/arp"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/qemu"
"github.com/pritunl/pritunl-cloud/scheduler"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
"github.com/sirupsen/logrus"
)
type StateOld struct {
nodeSelf *node.Node
nodes []*node.Node
nodeDatacenter *datacenter.Datacenter
nodeZone *zone.Zone
vxlan bool
zoneMap map[bson.ObjectID]*zone.Zone
namespaces []string
interfaces []string
interfacesSet set.Set
nodeFirewall []*firewall.Rule
firewalls map[string][]*firewall.Rule
firewallMaps map[string][]*firewall.Mapping
pools []*pool.Pool
disks []*disk.Disk
schedulers []*scheduler.Scheduler
deploymentsReservedMap map[bson.ObjectID]*deployment.Deployment
deploymentsDeployedMap map[bson.ObjectID]*deployment.Deployment
deploymentsInactiveMap map[bson.ObjectID]*deployment.Deployment
podsMap map[bson.ObjectID]*pod.Pod
unitsMap map[bson.ObjectID]*unit.Unit
specsMap map[bson.ObjectID]*spec.Spec
specsPodsMap map[bson.ObjectID]*pod.Pod
specsPodUnitsMap map[bson.ObjectID][]*unit.Unit
specsUnitsMap map[bson.ObjectID]*unit.Unit
specsDeploymentsMap map[bson.ObjectID]*deployment.Deployment
specsDomainsMap map[bson.ObjectID]*domain.Domain
specsSecretsMap map[bson.ObjectID]*secret.Secret
specsCertsMap map[bson.ObjectID]*certificate.Certificate
virtsMap map[bson.ObjectID]*vm.VirtualMachine
instances []*instance.Instance
instancesMap map[bson.ObjectID]*instance.Instance
instanceDisks map[bson.ObjectID][]*disk.Disk
instanceNamespaces map[bson.ObjectID][]string
authoritiesMap map[string][]*authority.Authority
vpcs []*vpc.Vpc
vpcsMap map[bson.ObjectID]*vpc.Vpc
vpcIpsMap map[bson.ObjectID][]*vpc.VpcIp
arpRecords map[string]set.Set
addInstances set.Set
remInstances set.Set
running []string
}
func (s *StateOld) Node() *node.Node {
return s.nodeSelf
}
func (s *StateOld) Nodes() []*node.Node {
return s.nodes
}
func (s *StateOld) VxLan() bool {
return s.vxlan
}
func (s *StateOld) NodeDatacenter() *datacenter.Datacenter {
return s.nodeDatacenter
}
func (s *StateOld) NodeZone() *zone.Zone {
return s.nodeZone
}
func (s *StateOld) GetZone(zneId bson.ObjectID) *zone.Zone {
return s.zoneMap[zneId]
}
func (s *StateOld) Namespaces() []string {
return s.namespaces
}
func (s *StateOld) Interfaces() []string {
return s.interfaces
}
func (s *StateOld) HasInterfaces(iface string) bool {
return s.interfacesSet.Contains(iface)
}
func (s *StateOld) Instances() []*instance.Instance {
return s.instances
}
func (s *StateOld) Schedulers() []*scheduler.Scheduler {
return s.schedulers
}
func (s *StateOld) NodeFirewall() []*firewall.Rule {
return s.nodeFirewall
}
func (s *StateOld) Firewalls() map[string][]*firewall.Rule {
return s.firewalls
}
func (s *StateOld) FirewallMaps() map[string][]*firewall.Mapping {
return s.firewallMaps
}
func (s *StateOld) Running() []string {
return s.running
}
func (s *StateOld) Disks() []*disk.Disk {
return s.disks
}
func (s *StateOld) GetInstaceDisks(instId bson.ObjectID) []*disk.Disk {
return s.instanceDisks[instId]
}
func (s *StateOld) GetInstanceNamespaces(instId bson.ObjectID) []string {
return s.instanceNamespaces[instId]
}
func (s *StateOld) GetInstaceAuthorities(roles []string) []*authority.Authority {
authrSet := set.NewSet()
authrs := []*authority.Authority{}
for _, role := range roles {
for _, authr := range s.authoritiesMap[role] {
if authrSet.Contains(authr.Id) {
continue
}
authrSet.Add(authr.Id)
authrs = append(authrs, authr)
}
}
return authrs
}
func (s *StateOld) DeploymentReserved(deplyId bson.ObjectID) *deployment.Deployment {
return s.deploymentsReservedMap[deplyId]
}
func (s *StateOld) DeploymentsReserved() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = s.deploymentsReservedMap
return
}
func (s *StateOld) DeploymentDeployed(deplyId bson.ObjectID) *deployment.Deployment {
return s.deploymentsDeployedMap[deplyId]
}
func (s *StateOld) DeploymentsDeployed() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = s.deploymentsDeployedMap
return
}
func (s *StateOld) DeploymentsDestroy() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = s.deploymentsInactiveMap
return
}
func (s *StateOld) DeploymentInactive(deplyId bson.ObjectID) *deployment.Deployment {
return s.deploymentsInactiveMap[deplyId]
}
func (s *StateOld) DeploymentsInactive() (
deplys map[bson.ObjectID]*deployment.Deployment) {
deplys = s.deploymentsInactiveMap
return
}
func (s *StateOld) Deployment(deplyId bson.ObjectID) (
deply *deployment.Deployment) {
deply = s.deploymentsDeployedMap[deplyId]
if deply != nil {
return
}
deply = s.deploymentsReservedMap[deplyId]
if deply != nil {
return
}
deply = s.deploymentsInactiveMap[deplyId]
if deply != nil {
return
}
return
}
func (s *StateOld) Pod(pdId bson.ObjectID) *pod.Pod {
return s.podsMap[pdId]
}
func (s *StateOld) Unit(unitId bson.ObjectID) *unit.Unit {
return s.unitsMap[unitId]
}
func (s *StateOld) Spec(commitId bson.ObjectID) *spec.Spec {
return s.specsMap[commitId]
}
func (s *StateOld) SpecPod(pdId bson.ObjectID) *pod.Pod {
return s.specsPodsMap[pdId]
}
func (s *StateOld) SpecPodUnits(pdId bson.ObjectID) []*unit.Unit {
return s.specsPodUnitsMap[pdId]
}
func (s *StateOld) SpecUnit(unitId bson.ObjectID) *unit.Unit {
return s.specsUnitsMap[unitId]
}
func (s *StateOld) SpecDomain(domnId bson.ObjectID) *domain.Domain {
return s.specsDomainsMap[domnId]
}
func (s *StateOld) SpecSecret(secrID bson.ObjectID) *secret.Secret {
return s.specsSecretsMap[secrID]
}
func (s *StateOld) SpecCert(certId bson.ObjectID) *certificate.Certificate {
return s.specsCertsMap[certId]
}
func (s *StateOld) Vpc(vpcId bson.ObjectID) *vpc.Vpc {
return s.vpcsMap[vpcId]
}
func (s *StateOld) VpcIps(vpcId bson.ObjectID) []*vpc.VpcIp {
return s.vpcIpsMap[vpcId]
}
func (s *StateOld) VpcIpsMap() map[bson.ObjectID][]*vpc.VpcIp {
return s.vpcIpsMap
}
func (s *StateOld) ArpRecords(namespace string) set.Set {
return s.arpRecords[namespace]
}
func (s *StateOld) Vpcs() []*vpc.Vpc {
return s.vpcs
}
func (s *StateOld) DiskInUse(instId, dskId bson.ObjectID) bool {
curVirt := s.virtsMap[instId]
if curVirt != nil {
if curVirt.State != vm.Stopped && curVirt.State != vm.Failed {
for _, vmDsk := range curVirt.Disks {
if vmDsk.GetId() == dskId {
return true
}
}
}
}
return false
}
func (s *StateOld) GetVirt(instId bson.ObjectID) *vm.VirtualMachine {
if instId.IsZero() {
return nil
}
return s.virtsMap[instId]
}
func (s *StateOld) GetInstace(instId bson.ObjectID) *instance.Instance {
if instId.IsZero() {
return nil
}
return s.instancesMap[instId]
}
func (s *StateOld) init() (err error) {
db := database.GetDatabase()
defer db.Close()
s.nodeSelf = node.Self.Copy()
// Datacenter
dcId := s.nodeSelf.Datacenter
if !dcId.IsZero() {
dc, e := datacenter.Get(db, dcId)
if e != nil {
err = e
return
}
s.nodeDatacenter = dc
}
// Datacenter
// Zone
zneId := s.nodeSelf.Zone
if !zneId.IsZero() {
zne, e := zone.Get(db, zneId)
if e != nil {
err = e
return
}
s.nodeZone = zne
}
// Zone
// Zones
if s.nodeDatacenter != nil && s.nodeDatacenter.Vxlan() {
s.vxlan = true
znes, e := zone.GetAllDatacenter(db, s.nodeDatacenter.Id)
if e != nil {
err = e
return
}
zonesMap := map[bson.ObjectID]*zone.Zone{}
for _, zne := range znes {
zonesMap[zne.Id] = zne
}
s.zoneMap = zonesMap
ndes, e := node.GetAllNet(db)
if e != nil {
err = e
return
}
s.nodes = ndes
}
// Zones
// Network
namespaces, err := utils.GetNamespaces()
if err != nil {
return
}
s.namespaces = namespaces
interfaces, interfacesSet, err := utils.GetInterfacesSet()
if err != nil {
return
}
s.interfaces = interfaces
s.interfacesSet = interfacesSet
// Network
// Pools
pools, err := pool.GetAll(db, &bson.M{
"zone": s.nodeSelf.Zone,
})
if err != nil {
return
}
s.pools = pools
// Pools
// Disks
disks, err := disk.GetNode(db, s.nodeSelf.Id, s.nodeSelf.Pools)
if err != nil {
return
}
s.disks = disks
instanceDisks := map[bson.ObjectID][]*disk.Disk{}
for _, dsk := range disks {
dsks := instanceDisks[dsk.Instance]
if dsks == nil {
dsks = []*disk.Disk{}
}
instanceDisks[dsk.Instance] = append(dsks, dsk)
}
s.instanceDisks = instanceDisks
// Disks
// Vpcs
vpcs := []*vpc.Vpc{}
vpcsId := []bson.ObjectID{}
vpcsMap := map[bson.ObjectID]*vpc.Vpc{}
if s.nodeDatacenter != nil {
vpcs, err = vpc.GetDatacenter(db, s.nodeDatacenter.Id)
if err != nil {
return
}
for _, vc := range vpcs {
vpcsId = append(vpcsId, vc.Id)
vpcsMap[vc.Id] = vc
}
}
s.vpcs = vpcs
s.vpcsMap = vpcsMap
vpcIpsMap := map[bson.ObjectID][]*vpc.VpcIp{}
if s.nodeDatacenter != nil {
vpcIpsMap, err = vpc.GetIpsMapped(db, vpcsId)
if err != nil {
return
}
}
s.vpcIpsMap = vpcIpsMap
// Vpcs
// Deployments
deployments, err := deployment.GetAll(db, &bson.M{
"node": node.Self.Id,
})
if err != nil {
return
}
deploymentsNode := map[bson.ObjectID]*deployment.Deployment{}
deploymentsReservedMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsDeployedMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsInactiveMap := map[bson.ObjectID]*deployment.Deployment{}
deploymentsIdSet := set.NewSet()
podIdsSet := set.NewSet()
unitIdsSet := set.NewSet()
specIdsSet := set.NewSet()
for _, deply := range deployments {
deploymentsNode[deply.Id] = deply
deploymentsIdSet.Add(deply.Id)
switch deply.State {
case deployment.Reserved:
deploymentsReservedMap[deply.Id] = deply
break
case deployment.Deployed:
switch deply.Action {
case deployment.Destroy, deployment.Archive,
deployment.Restore:
deploymentsInactiveMap[deply.Id] = deply
break
default:
deploymentsDeployedMap[deply.Id] = deply
}
break
case deployment.Archived:
deploymentsInactiveMap[deply.Id] = deply
break
}
podIdsSet.Add(deply.Pod)
unitIdsSet.Add(deply.Unit)
specIdsSet.Add(deply.Spec)
}
specIds := []bson.ObjectID{}
for specId := range specIdsSet.Iter() {
specIds = append(specIds, specId.(bson.ObjectID))
}
specs := []*spec.Spec{}
if len(specIds) > 0 {
specs, err = spec.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specIds,
},
})
if err != nil {
return
}
}
specSecretsSet := set.NewSet()
specCertsSet := set.NewSet()
specPodsSet := set.NewSet()
specUnitsSet := set.NewSet()
specDomainsSet := set.NewSet()
specsMap := map[bson.ObjectID]*spec.Spec{}
for _, spc := range specs {
specsMap[spc.Id] = spc
if spc.Instance != nil {
if spc.Instance.Pods != nil {
for _, pdId := range spc.Instance.Pods {
specPodsSet.Add(pdId)
}
}
if spc.Instance.Secrets != nil {
for _, secrId := range spc.Instance.Secrets {
specSecretsSet.Add(secrId)
}
}
if spc.Instance.Certificates != nil {
for _, certId := range spc.Instance.Certificates {
specCertsSet.Add(certId)
}
}
}
if spc.Firewall != nil {
for _, rule := range spc.Firewall.Ingress {
for _, ref := range rule.Sources {
specUnitsSet.Add(ref.Id)
}
}
}
if spc.Domain != nil {
for _, record := range spc.Domain.Records {
specDomainsSet.Add(record.Domain)
}
}
}
s.specsMap = specsMap
specCertIds := []bson.ObjectID{}
for certId := range specCertsSet.Iter() {
specCertIds = append(specCertIds, certId.(bson.ObjectID))
}
specsCertsMap := map[bson.ObjectID]*certificate.Certificate{}
specCerts := []*certificate.Certificate{}
if len(specCertIds) > 0 {
specCerts, err = certificate.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specCertIds,
},
})
if err != nil {
return
}
}
for _, specCert := range specCerts {
specsCertsMap[specCert.Id] = specCert
}
s.specsCertsMap = specsCertsMap
specSecretIds := []bson.ObjectID{}
for secrId := range specSecretsSet.Iter() {
specSecretIds = append(specSecretIds, secrId.(bson.ObjectID))
}
specsSecretsMap := map[bson.ObjectID]*secret.Secret{}
specSecrets := []*secret.Secret{}
if len(specSecretIds) > 0 {
specSecrets, err = secret.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specSecretIds,
},
})
if err != nil {
return
}
}
for _, specSecret := range specSecrets {
specsSecretsMap[specSecret.Id] = specSecret
}
s.specsSecretsMap = specsSecretsMap
specPodIds := []bson.ObjectID{}
for pdId := range specPodsSet.Iter() {
specPodIds = append(specPodIds, pdId.(bson.ObjectID))
}
specPods := []*pod.Pod{}
if len(specPodIds) > 0 {
specPods, err = pod.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specPodIds,
},
})
if err != nil {
return
}
}
specsPodsMap := map[bson.ObjectID]*pod.Pod{}
for _, specPod := range specPods {
specsPodsMap[specPod.Id] = specPod
}
s.specsPodsMap = specsPodsMap
specUnitIds := []bson.ObjectID{}
for unitId := range specUnitsSet.Iter() {
specUnitIds = append(specUnitIds, unitId.(bson.ObjectID))
}
specUnits := []*unit.Unit{}
if len(specUnitIds) > 0 || len(specPodIds) > 0 {
specUnits, err = unit.GetAll(db, &bson.M{
"$or": []*bson.M{
&bson.M{
"_id": &bson.M{
"$in": specUnitIds,
},
},
&bson.M{
"pod": &bson.M{
"$in": specPodIds,
},
},
},
})
if err != nil {
return
}
}
specDeploymentsSet := set.NewSet()
specsUnitsMap := map[bson.ObjectID]*unit.Unit{}
specsPodUnitsMap := map[bson.ObjectID][]*unit.Unit{}
for _, specUnit := range specUnits {
specsUnitsMap[specUnit.Id] = specUnit
specsPodUnitsMap[specUnit.Pod] = append(
specsPodUnitsMap[specUnit.Pod], specUnit)
for _, deplyId := range specUnit.Deployments {
specDeploymentsSet.Add(deplyId)
}
}
s.specsUnitsMap = specsUnitsMap
s.specsPodUnitsMap = specsPodUnitsMap
specDomainIds := []bson.ObjectID{}
for pdId := range specDomainsSet.Iter() {
specDomainIds = append(specDomainIds, pdId.(bson.ObjectID))
}
specsDomainsMap := map[bson.ObjectID]*domain.Domain{}
specDomains, err := domain.GetLoadedAllIds(db, specDomainIds)
if err != nil {
return
}
for _, specDomain := range specDomains {
specsDomainsMap[specDomain.Id] = specDomain
}
s.specsDomainsMap = specsDomainsMap
specDeploymentIds := []bson.ObjectID{}
for deplyIdInf := range specDeploymentsSet.Iter() {
deplyId := deplyIdInf.(bson.ObjectID)
if !deploymentsIdSet.Contains(deplyId) {
specDeploymentIds = append(specDeploymentIds, deplyId)
}
}
specDeployments := []*deployment.Deployment{}
if len(specDeploymentIds) > 0 {
specDeployments, err = deployment.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specDeploymentIds,
},
})
if err != nil {
return
}
}
for _, specDeployment := range specDeployments {
deploymentsIdSet.Add(specDeployment.Id)
switch specDeployment.State {
case deployment.Reserved:
deploymentsReservedMap[specDeployment.Id] = specDeployment
break
case deployment.Deployed:
switch specDeployment.Action {
case deployment.Destroy, deployment.Archive,
deployment.Restore:
deploymentsInactiveMap[specDeployment.Id] = specDeployment
break
default:
deploymentsDeployedMap[specDeployment.Id] = specDeployment
}
break
case deployment.Archived:
deploymentsInactiveMap[specDeployment.Id] = specDeployment
break
}
}
podIds := []bson.ObjectID{}
for podId := range podIdsSet.Iter() {
podIds = append(podIds, podId.(bson.ObjectID))
}
pods := []*pod.Pod{}
if len(podIds) > 0 {
pods, err = pod.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": podIds,
},
})
if err != nil {
return
}
}
podsMap := map[bson.ObjectID]*pod.Pod{}
for _, pd := range pods {
podsMap[pd.Id] = pd
}
s.podsMap = podsMap
unitIds := []bson.ObjectID{}
for unitId := range unitIdsSet.Iter() {
unitIds = append(unitIds, unitId.(bson.ObjectID))
}
units := []*unit.Unit{}
if len(unitIds) > 0 {
units, err = unit.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": unitIds,
},
})
if err != nil {
return
}
}
unitsMap := map[bson.ObjectID]*unit.Unit{}
podDeploymentsSet := set.NewSet()
for _, unt := range units {
unitsMap[unt.Id] = unt
for _, deplyId := range unt.Deployments {
podDeploymentsSet.Add(deplyId)
}
}
s.unitsMap = unitsMap
podDeploymentIds := []bson.ObjectID{}
for deplyIdInf := range podDeploymentsSet.Iter() {
deplyId := deplyIdInf.(bson.ObjectID)
if !deploymentsIdSet.Contains(deplyId) {
podDeploymentIds = append(podDeploymentIds, deplyId)
}
}
podDeployments := []*deployment.Deployment{}
if len(podDeploymentIds) > 0 {
podDeployments, err = deployment.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": podDeploymentIds,
},
})
if err != nil {
return
}
}
for _, podDeployment := range podDeployments {
deploymentsIdSet.Add(podDeployment.Id)
switch podDeployment.State {
case deployment.Reserved:
deploymentsReservedMap[podDeployment.Id] = podDeployment
break
case deployment.Deployed:
switch podDeployment.Action {
case deployment.Destroy, deployment.Archive,
deployment.Restore:
deploymentsInactiveMap[podDeployment.Id] = podDeployment
break
default:
deploymentsDeployedMap[podDeployment.Id] = podDeployment
}
break
case deployment.Archived:
deploymentsInactiveMap[podDeployment.Id] = podDeployment
break
}
}
s.deploymentsReservedMap = deploymentsReservedMap
s.deploymentsDeployedMap = deploymentsDeployedMap
s.deploymentsInactiveMap = deploymentsInactiveMap
// Deployments
// Instances
instances, err := instance.GetAllVirtMapped(db, &bson.M{
"node": s.nodeSelf.Id,
}, s.pools, instanceDisks)
if err != nil {
return
}
s.instances = instances
nodePortsMap := map[string][]*nodeport.Mapping{}
instId := set.NewSet()
instancesMap := map[bson.ObjectID]*instance.Instance{}
instancesRolesSet := set.NewSet()
for _, inst := range instances {
instId.Add(inst.Id)
instancesMap[inst.Id] = inst
nodePortsMap[inst.NetworkNamespace] = append(
nodePortsMap[inst.NetworkNamespace], inst.NodePorts...)
for _, role := range inst.Roles {
instancesRolesSet.Add(role)
}
}
s.instancesMap = instancesMap
instancesRoles := []string{}
for instRoleInf := range instancesRolesSet.Iter() {
instancesRoles = append(instancesRoles, instRoleInf.(string))
}
authrsMap, err := authority.GetMapRoles(db, &bson.M{
"roles": &bson.M{
"$in": instancesRoles,
},
})
if err != nil {
return
}
s.authoritiesMap = authrsMap
// Instances
// Virtuals
curVirts, err := qemu.GetVms(db)
if err != nil {
return
}
virtsMap := map[bson.ObjectID]*vm.VirtualMachine{}
for _, virt := range curVirts {
if !instId.Contains(virt.Id) {
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("sync: Unknown instance")
}
virtsMap[virt.Id] = virt
}
s.virtsMap = virtsMap
// Virtuals
// Firewalls
s.arpRecords = arp.BuildState(s.instances, s.vpcsMap, s.vpcIpsMap)
// Firewalls
// Firewalls
specRules, err := firewall.GetSpecRules(instances, deploymentsNode,
specsMap, specsUnitsMap, deploymentsDeployedMap)
if err != nil {
return
}
nodeFirewall, firewalls, firewallMaps, instNamespaces, err :=
firewall.GetAllIngress(db, s.nodeSelf, instances,
specRules, nodePortsMap)
if err != nil {
return
}
s.nodeFirewall = nodeFirewall
s.firewalls = firewalls
s.firewallMaps = firewallMaps
s.instanceNamespaces = instNamespaces
// Firewalls
// Schedulers
schedulers, err := scheduler.GetAll(db)
if err != nil {
return
}
s.schedulers = schedulers
// Schedulers
// Running
items, err := ioutil.ReadDir("/var/run")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "state: Failed to read run directory"),
}
return
}
running := []string{}
for _, item := range items {
if !item.IsDir() {
running = append(running, item.Name())
}
}
s.running = running
// Running
return
}
================================================
FILE: state/virtuals.go
================================================
package state
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/qemu"
"github.com/pritunl/pritunl-cloud/vm"
)
var (
Virtuals = &VirtualsState{}
VirtualsPkg = NewPackage(Virtuals)
)
type VirtualsState struct {
virtsMap map[bson.ObjectID]*vm.VirtualMachine
}
func (p *VirtualsState) DiskInUse(instId, dskId bson.ObjectID) bool {
curVirt := p.virtsMap[instId]
if curVirt != nil {
if curVirt.State != vm.Stopped && curVirt.State != vm.Failed {
for _, vmDsk := range curVirt.Disks {
if vmDsk.GetId() == dskId {
return true
}
}
}
}
return false
}
func (p *VirtualsState) GetVirt(instId bson.ObjectID) *vm.VirtualMachine {
if instId.IsZero() {
return nil
}
return p.virtsMap[instId]
}
func (p *VirtualsState) VirtsMap() map[bson.ObjectID]*vm.VirtualMachine {
return p.virtsMap
}
func (p *VirtualsState) Refresh(pkg *Package,
db *database.Database) (err error) {
curVirts, err := qemu.GetVms(db)
if err != nil {
return
}
virtsMap := map[bson.ObjectID]*vm.VirtualMachine{}
for _, virt := range curVirts {
virtsMap[virt.Id] = virt
}
p.virtsMap = virtsMap
return
}
func (p *VirtualsState) Apply(st *State) {
st.DiskInUse = p.DiskInUse
st.GetVirt = p.GetVirt
st.VirtsMap = p.VirtsMap
}
================================================
FILE: state/vpcs.go
================================================
package state
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/vpc"
)
var (
Vpcs = &VpcsState{}
VpcsPkg = NewPackage(Vpcs)
)
type VpcsState struct {
vpcs []*vpc.Vpc
vpcsMap map[bson.ObjectID]*vpc.Vpc
vpcIpsMap map[bson.ObjectID][]*vpc.VpcIp
}
func (p *VpcsState) Vpc(vpcId bson.ObjectID) *vpc.Vpc {
return p.vpcsMap[vpcId]
}
func (p *VpcsState) VpcsMap() map[bson.ObjectID]*vpc.Vpc {
return p.vpcsMap
}
func (p *VpcsState) VpcIps(vpcId bson.ObjectID) []*vpc.VpcIp {
return p.vpcIpsMap[vpcId]
}
func (p *VpcsState) VpcIpsMap() map[bson.ObjectID][]*vpc.VpcIp {
return p.vpcIpsMap
}
func (p *VpcsState) Vpcs() []*vpc.Vpc {
return p.vpcs
}
func (p *VpcsState) Refresh(pkg *Package,
db *database.Database) (err error) {
dcId := node.Self.Datacenter
vpcsId := []bson.ObjectID{}
vpcsMap := map[bson.ObjectID]*vpc.Vpc{}
if dcId.IsZero() {
p.vpcs = nil
p.vpcsMap = map[bson.ObjectID]*vpc.Vpc{}
p.vpcIpsMap = map[bson.ObjectID][]*vpc.VpcIp{}
return
}
vpcs, err := vpc.GetDatacenter(db, dcId)
if err != nil {
return
}
for _, vc := range vpcs {
vpcsId = append(vpcsId, vc.Id)
vpcsMap[vc.Id] = vc
}
p.vpcs = vpcs
p.vpcsMap = vpcsMap
vpcIpsMap, err := vpc.GetIpsMapped(db, vpcsId)
if err != nil {
return
}
p.vpcIpsMap = vpcIpsMap
return
}
func (p *VpcsState) Apply(st *State) {
st.Vpc = p.Vpc
st.VpcsMap = p.VpcsMap
st.VpcIps = p.VpcIps
st.VpcIpsMap = p.VpcIpsMap
st.Vpcs = p.Vpcs
}
================================================
FILE: state/zone.go
================================================
package state
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/zone"
)
var (
Zone = &ZoneState{}
ZonePkg = NewPackage(Zone)
)
type ZoneState struct {
nodeZone *zone.Zone
}
func (p *ZoneState) NodeZone() *zone.Zone {
return p.nodeZone
}
func (p *ZoneState) Refresh(pkg *Package,
db *database.Database) (err error) {
zneId := node.Self.Zone
if zneId.IsZero() {
p.nodeZone = nil
pkg.Evict()
return
}
zne, e := zone.Get(db, zneId)
if e != nil {
err = e
return
}
p.nodeZone = zne
pkg.Cache(15 * time.Second)
return
}
func (p *ZoneState) Apply(st *State) {
st.NodeZone = p.NodeZone
}
================================================
FILE: state/zones.go
================================================
package state
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/zone"
)
var (
Zones = &ZonesState{}
ZonesPkg = NewPackage(Zones)
)
type ZonesState struct {
vxlan bool
zoneMap map[bson.ObjectID]*zone.Zone
nodes []*node.Node
}
func (p *ZonesState) VxLan() bool {
return p.vxlan
}
func (p *ZonesState) GetZone(zneId bson.ObjectID) *zone.Zone {
return p.zoneMap[zneId]
}
func (p *ZonesState) Nodes() []*node.Node {
return p.nodes
}
func (p *ZonesState) Refresh(pkg *Package,
db *database.Database) (err error) {
nodeDc := Datacenter.NodeDatacenter()
if nodeDc == nil || !nodeDc.Vxlan() {
p.vxlan = false
p.zoneMap = nil
p.nodes = nil
pkg.Evict()
return
}
p.vxlan = true
znes, e := zone.GetAllDatacenter(db, nodeDc.Id)
if e != nil {
err = e
return
}
zonesMap := map[bson.ObjectID]*zone.Zone{}
for _, zne := range znes {
zonesMap[zne.Id] = zne
}
p.zoneMap = zonesMap
ndes, e := node.GetAllNet(db)
if e != nil {
err = e
return
}
p.nodes = ndes
pkg.Cache(10 * time.Second)
return
}
func (p *ZonesState) Apply(st *State) {
st.VxLan = p.VxLan
st.GetZone = p.GetZone
st.Nodes = p.Nodes
}
func init() {
ZonesPkg.
After(Datacenter)
}
================================================
FILE: static/file.go
================================================
package static
import (
"bytes"
"compress/gzip"
"crypto/md5"
"encoding/base32"
"io/ioutil"
"path/filepath"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var (
mimeTypes = map[string]string{
".js": "application/javascript",
".json": "application/json",
".css": "text/css",
".html": "text/html",
".jpg": "image/jpeg",
".png": "image/png",
".svg": "image/svg+xml",
".ico": "image/vnd.microsoft.icon",
".otf": "application/font-sfnt",
".ttf": "application/font-sfnt",
".woff": "application/font-woff",
".woff2": "font/woff2",
".ijmap": "text/plain",
".eot": "application/vnd.ms-fontobject",
".map": "application/json",
}
)
type File struct {
Type string
Hash string
Data []byte
GzipData []byte
}
func NewFile(path string) (file *File, err error) {
ext := filepath.Ext(path)
if len(ext) == 0 {
return
}
typ, ok := mimeTypes[ext]
if !ok {
return
}
data, e := ioutil.ReadFile(path)
if e != nil {
err = &errortypes.ReadError{
errors.Wrap(e, "static: Read error"),
}
return
}
hash := md5.Sum(data)
hashStr := base32.StdEncoding.EncodeToString(hash[:])
hashStr = strings.Replace(hashStr, "=", "", -1)
hashStr = strings.ToLower(hashStr)
file = &File{
Type: typ,
Hash: hashStr,
Data: data,
}
gzipData := &bytes.Buffer{}
writer, err := gzip.NewWriterLevel(gzipData, gzip.BestCompression)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "static: Gzip error"),
}
return
}
writer.Write(file.Data)
writer.Close()
file.GzipData = gzipData.Bytes()
return
}
================================================
FILE: static/static.go
================================================
// Versions static files with hash, replaces references and stores in memory.
package static
import (
"io/ioutil"
"path"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type Store struct {
Files map[string]*File
root string
}
func (s *Store) addDir(dir string) (err error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "static: Read directory error"),
}
return
}
for _, info := range files {
name := info.Name()
fullPath := path.Join(dir, name)
if info.IsDir() {
s.addDir(fullPath)
continue
}
file, e := NewFile(fullPath)
if e != nil {
err = e
return
}
if file != nil {
s.Files[fullPath] = file
}
}
return
}
func NewStore(root string) (store *Store, err error) {
store = &Store{
Files: map[string]*File{},
root: root,
}
err = store.addDir(root)
if err != nil {
time.Sleep(3 * time.Second)
err = store.addDir(root)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "static: Init error"),
}
return
}
}
return
}
func GetMimeType(name string) string {
return mimeTypes[path.Ext(name)]
}
================================================
FILE: storage/constants.go
================================================
package storage
const (
Public = "public"
Private = "private"
Web = "web"
AwsStandard = "aws_standard"
AwsInfrequentAccess = "aws_infrequent_access"
AwsGlacier = "aws_glacier"
OracleStandard = "oracle_standard"
OracleArchive = "oracle_archive"
)
================================================
FILE: storage/storage.go
================================================
package storage
import (
"net/url"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Storage struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
Type string `bson:"type" json:"type"`
Endpoint string `bson:"endpoint" json:"endpoint"`
Bucket string `bson:"bucket" json:"bucket"`
AccessKey string `bson:"access_key" json:"access_key"`
SecretKey string `bson:"secret_key" json:"secret_key"`
Insecure bool `bson:"insecure" json:"insecure"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Type string `bson:"type" json:"type"`
}
func (s *Storage) IsOracle() bool {
return strings.Contains(strings.ToLower(s.Endpoint), "oracle")
}
func (s *Storage) GetWebUrl() (u *url.URL) {
u = &url.URL{}
if s.Insecure {
u.Scheme = "http"
} else {
u.Scheme = "https"
}
u.Host = s.Endpoint
u.Path = "/" + s.Bucket
return
}
func (s *Storage) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
s.Name = utils.FilterName(s.Name)
switch s.Type {
case Public:
break
case Private:
break
case Web:
break
case "":
s.Type = Public
break
default:
errData = &errortypes.ErrorData{
Error: "invalid_type",
Message: "Storage type is invalid",
}
return
}
return
}
func (s *Storage) Commit(db *database.Database) (err error) {
coll := db.Storages()
err = coll.Commit(s.Id, s)
if err != nil {
return
}
return
}
func (s *Storage) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Storages()
err = coll.CommitFields(s.Id, s, fields)
if err != nil {
return
}
return
}
func (s *Storage) Insert(db *database.Database) (err error) {
coll := db.Storages()
if !s.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("storage: Storage already exists"),
}
return
}
resp, err := coll.InsertOne(db, s)
if err != nil {
err = database.ParseError(err)
return
}
s.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: storage/utils.go
================================================
package storage
import (
"strings"
minio "github.com/minio/minio-go/v7"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, storeId bson.ObjectID) (
store *Storage, err error) {
coll := db.Storages()
store = &Storage{}
err = coll.FindOneId(storeId, store)
if err != nil {
return
}
return
}
func GetAll(db *database.Database) (stores []*Storage, err error) {
coll := db.Storages()
stores = []*Storage{}
cursor, err := coll.Find(
db,
&bson.M{},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
store := &Storage{}
err = cursor.Decode(store)
if err != nil {
err = database.ParseError(err)
return
}
stores = append(stores, store)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (stores []*Storage, count int64, err error) {
coll := db.Storages()
stores = []*Storage{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
store := &Storage{}
err = cursor.Decode(store)
if err != nil {
err = database.ParseError(err)
return
}
stores = append(stores, store)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, storeId bson.ObjectID) (err error) {
coll := db.Images()
_, err = coll.DeleteMany(db, &bson.M{
"storage": storeId,
})
if err != nil {
return
}
coll = db.Storages()
_, err = coll.DeleteOne(db, &bson.M{
"_id": storeId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, storeIds []bson.ObjectID) (
err error) {
coll := db.Images()
for _, storeId := range storeIds {
_, err = coll.DeleteMany(db, &bson.M{
"storage": storeId,
})
if err != nil {
return
}
}
coll = db.Storages()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": storeIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func FormatStorageClass(class string) string {
switch class {
case AwsStandard:
return "STANDARD"
case AwsInfrequentAccess:
return "STANDARD_IA"
case AwsGlacier:
return "GLACIER"
}
return ""
}
func ParseStorageClass(obj minio.ObjectInfo) string {
opcRequestId := obj.Metadata.Get("Opc-Request-Id")
archivalState := strings.ToLower(obj.Metadata.Get("Archival-State"))
if archivalState != "" {
return OracleArchive
} else if opcRequestId != "" {
return OracleStandard
}
switch obj.StorageClass {
case "STANDARD":
return AwsStandard
case "STANDARD_IA":
return AwsInfrequentAccess
case "GLACIER":
return AwsGlacier
}
return ""
}
================================================
FILE: store/address.go
================================================
package store
import (
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
)
var (
addressStores = map[bson.ObjectID]*AddressStore{}
addressStoresLock = sync.Mutex{}
)
type AddressStore struct {
Addr string
Addr6 string
Timestamp time.Time
Refresh time.Duration
}
func GetAddress(virtId bson.ObjectID) (
addressStore *AddressStore, ok bool) {
addressStoresLock.Lock()
addressStore, ok = addressStores[virtId]
addressStoresLock.Unlock()
ttl := settings.Hypervisor.AddressRefreshTtl
if ok && ttl != 0 && time.Since(addressStore.Timestamp) > time.Duration(
ttl)*time.Second && node.Self.IsDhcp6() {
ok = false
}
return
}
func SetAddress(virtId bson.ObjectID, addr, addr6 string) {
addressStoresLock.Lock()
now := time.Now()
addressStore := addressStores[virtId]
if addressStore != nil && addressStore.Refresh != 0 {
refreshTtl := time.Duration(
settings.Hypervisor.AddressRefreshTtl) * time.Second
now = now.Add(-refreshTtl).Add(addressStore.Refresh)
}
addressStores[virtId] = &AddressStore{
Addr: addr,
Addr6: addr6,
Timestamp: now,
}
addressStoresLock.Unlock()
}
func SetAddressExpire(virtId bson.ObjectID, ttl time.Duration) {
addressStoresLock.Lock()
addressStore, ok := addressStores[virtId]
if ok {
refreshTtl := time.Duration(
settings.Hypervisor.AddressRefreshTtl) * time.Second
addressStore.Timestamp = time.Now().Add(-refreshTtl).Add(ttl)
}
addressStoresLock.Unlock()
}
func SetAddressExpireMulti(virtId bson.ObjectID,
ttl, ttl2 time.Duration) {
addressStoresLock.Lock()
addressStore, ok := addressStores[virtId]
if ok {
refreshTtl := time.Duration(
settings.Hypervisor.AddressRefreshTtl) * time.Second
addressStore.Timestamp = time.Now().Add(-refreshTtl).Add(ttl)
addressStore.Refresh = ttl2
}
addressStoresLock.Unlock()
}
func RemAddress(addressId bson.ObjectID) {
addressStoresLock.Lock()
delete(addressStores, addressId)
addressStoresLock.Unlock()
}
================================================
FILE: store/arp.go
================================================
package store
import (
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
var (
arpStores = map[bson.ObjectID]ArpStore{}
arpStoresLock = sync.Mutex{}
)
type ArpStore struct {
Records set.Set
Timestamp time.Time
}
func GetArp(instId bson.ObjectID) (arpStore ArpStore, ok bool) {
arpStoresLock.Lock()
arpStore, ok = arpStores[instId]
arpStoresLock.Unlock()
if ok {
arpStore.Records = arpStore.Records.Copy()
}
return
}
func SetArp(instId bson.ObjectID, records set.Set) {
arpStoresLock.Lock()
arpStores[instId] = ArpStore{
Records: records.Copy(),
Timestamp: time.Now(),
}
arpStoresLock.Unlock()
}
func RemArp(instId bson.ObjectID) {
arpStoresLock.Lock()
delete(arpStores, instId)
arpStoresLock.Unlock()
}
================================================
FILE: store/disks.go
================================================
package store
import (
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/vm"
)
var (
disksStores = map[bson.ObjectID]DisksStore{}
disksStoresLock = sync.Mutex{}
)
type DisksStore struct {
Disks []vm.Disk
Timestamp time.Time
}
func GetDisks(virtId bson.ObjectID) (disksStore DisksStore, ok bool) {
disksStoresLock.Lock()
disksStore, ok = disksStores[virtId]
disksStoresLock.Unlock()
if ok {
disksStore.Disks = append([]vm.Disk{}, disksStore.Disks...)
}
return
}
func SetDisks(virtId bson.ObjectID, disks []*vm.Disk) {
disksRef := []vm.Disk{}
for _, dsk := range disks {
disksRef = append(disksRef, *dsk)
}
disksStoresLock.Lock()
disksStores[virtId] = DisksStore{
Disks: disksRef,
Timestamp: time.Now(),
}
disksStoresLock.Unlock()
}
func RemDisks(virtId bson.ObjectID) {
disksStoresLock.Lock()
delete(disksStores, virtId)
disksStoresLock.Unlock()
}
================================================
FILE: store/routes.go
================================================
package store
import (
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/vpc"
)
var (
routesStores = map[bson.ObjectID]RoutesStore{}
routesStoresLock = sync.Mutex{}
)
type RoutesStore struct {
IcmpRedirects bool
Routes []vpc.Route
Routes6 []vpc.Route
Timestamp time.Time
}
func GetRoutes(instId bson.ObjectID) (routesStore RoutesStore, ok bool) {
routesStoresLock.Lock()
routesStore, ok = routesStores[instId]
routesStoresLock.Unlock()
if ok {
routesStore.Routes = append([]vpc.Route{}, routesStore.Routes...)
}
return
}
func SetRoutes(instId bson.ObjectID, icmpRedirects bool,
routes, routes6 []vpc.Route) {
routesStoresLock.Lock()
routesStores[instId] = RoutesStore{
IcmpRedirects: icmpRedirects,
Routes: append([]vpc.Route{}, routes...),
Routes6: append([]vpc.Route{}, routes6...),
Timestamp: time.Now(),
}
routesStoresLock.Unlock()
}
func RemRoutes(instId bson.ObjectID) {
routesStoresLock.Lock()
delete(routesStores, instId)
routesStoresLock.Unlock()
}
================================================
FILE: store/usb.go
================================================
package store
import (
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/vm"
)
var (
usbsStores = map[bson.ObjectID]UsbsStore{}
usbsStoresLock = sync.Mutex{}
)
type UsbsStore struct {
Usbs []vm.UsbDevice
Timestamp time.Time
}
func GetUsbs(virtId bson.ObjectID) (usbsStore UsbsStore, ok bool) {
usbsStoresLock.Lock()
usbsStore, ok = usbsStores[virtId]
usbsStoresLock.Unlock()
if ok {
usbsStore.Usbs = append([]vm.UsbDevice{}, usbsStore.Usbs...)
}
return
}
func SetUsbs(virtId bson.ObjectID, usbs []*vm.UsbDevice) {
usbsRef := []vm.UsbDevice{}
for _, dsk := range usbs {
usbsRef = append(usbsRef, *dsk)
}
usbsStoresLock.Lock()
usbsStores[virtId] = UsbsStore{
Usbs: usbsRef,
Timestamp: time.Now(),
}
usbsStoresLock.Unlock()
}
func RemUsbs(virtId bson.ObjectID) {
usbsStoresLock.Lock()
delete(usbsStores, virtId)
usbsStoresLock.Unlock()
}
================================================
FILE: store/virt.go
================================================
package store
import (
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/vm"
)
var (
virtStores = map[bson.ObjectID]VirtStore{}
virtStoresLock = sync.Mutex{}
)
type VirtStore struct {
Virt vm.VirtualMachine
Timestamp time.Time
}
func GetVirt(virtId bson.ObjectID) (virtStore VirtStore, ok bool) {
virtStoresLock.Lock()
virtStore, ok = virtStores[virtId]
virtStoresLock.Unlock()
return
}
func SetVirt(virtId bson.ObjectID, virt *vm.VirtualMachine) {
virtRef := *virt
virtRef.Disks = nil
virtStoresLock.Lock()
virtStores[virtId] = VirtStore{
Virt: virtRef,
Timestamp: time.Now(),
}
virtStoresLock.Unlock()
}
func RemVirt(virtId bson.ObjectID) {
virtStoresLock.Lock()
delete(virtStores, virtId)
virtStoresLock.Unlock()
}
================================================
FILE: subscription/subscription.go
================================================
package subscription
import (
"bytes"
"encoding/json"
"net/http"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/sirupsen/logrus"
)
var (
Sub = &Subscription{}
client = &http.Client{
Timeout: 30 * time.Second,
}
)
type Subscription struct {
Active bool `json:"active"`
Status string `json:"status"`
Plan string `json:"plan"`
Quantity int `json:"quantity"`
Amount int `json:"amount"`
PeriodEnd time.Time `json:"period_end"`
TrialEnd time.Time `json:"trial_end"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
Balance int64 `json:"balance"`
UrlKey string `json:"url_key"`
}
type subscriptionData struct {
Active bool `json:"active"`
Status string `json:"status"`
Plan string `json:"plan"`
Quantity int `json:"quantity"`
Amount int `json:"amount"`
PeriodEnd int64 `json:"period_end"`
TrialEnd int64 `json:"trial_end"`
CancelAtPeriodEnd bool `json:"cancel_at_period_end"`
Balance int64 `json:"balance"`
UrlKey string `json:"url_key"`
}
func Update() (errData *errortypes.ErrorData, err error) {
sub := &Subscription{}
if settings.System.License == "" {
Sub = sub
return
}
data, err := json.Marshal(struct {
Id string `json:"id"`
License string `json:"license"`
}{
Id: settings.System.Name,
License: settings.System.License,
})
req, err := http.NewRequest(
"GET",
"https://app.pritunl.com/subscription",
bytes.NewBuffer(data),
)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "subscription: Subscription request failed"),
}
return
}
req.Header.Set("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "subscription: Subscription request failed"),
}
return
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
errData = &errortypes.ErrorData{}
err = json.NewDecoder(resp.Body).Decode(errData)
if err != nil {
errData = nil
} else {
logrus.WithFields(logrus.Fields{
"error": errData.Error,
"error_msg": errData.Message,
}).Error("subscription: Subscription error")
}
err = &errortypes.RequestError{
errors.Wrap(err, "subscription: Subscription server error"),
}
return
}
subData := &subscriptionData{}
err = json.NewDecoder(resp.Body).Decode(subData)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(
err,
"subscription: Failed to parse subscription response",
),
}
return
}
if !strings.Contains(subData.Plan, "zero") &&
!strings.Contains(subData.Plan, "cloud") {
errData = &errortypes.ErrorData{
Error: "invalid_plan",
Message: "Invalid subscription plan",
}
err = &errortypes.RequestError{
errors.Wrap(err, "subscription: Invalid plan"),
}
return
}
sub.Active = subData.Active
sub.Status = subData.Status
sub.Plan = subData.Plan
sub.Quantity = subData.Quantity
sub.Amount = subData.Amount
sub.CancelAtPeriodEnd = subData.CancelAtPeriodEnd
sub.Balance = subData.Balance
sub.UrlKey = subData.UrlKey
if subData.PeriodEnd != 0 {
sub.PeriodEnd = time.Unix(subData.PeriodEnd, 0)
}
if subData.TrialEnd != 0 {
sub.TrialEnd = time.Unix(subData.TrialEnd, 0)
}
Sub = sub
return
}
func update() {
for {
time.Sleep(30 * time.Minute)
if constants.Shutdown {
return
}
err, _ := Update()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("subscription: Update error")
return
}
}
}
func init() {
module := requires.New("subscription")
module.After("settings")
module.Handler = func() (err error) {
Update()
go update()
return
}
}
================================================
FILE: sync/auth.go
================================================
package sync
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/user"
"github.com/sirupsen/logrus"
)
func authSync() (err error) {
db := database.GetDatabase()
defer db.Close()
coll := db.Users()
count, err := coll.CountDocuments(
db,
&bson.M{
"type": user.Local,
},
options.Count().SetLimit(1),
)
if err != nil {
err = database.ParseError(err)
return
}
settings.Local.NoLocalAuth = count == 0
return
}
func authRunner() {
time.Sleep(1 * time.Second)
for {
time.Sleep(10 * time.Second)
if constants.Shutdown {
return
}
err := authSync()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("sync: Failed to sync authentication status")
}
}
}
func initAuth() {
go authRunner()
}
================================================
FILE: sync/nodes.go
================================================
package sync
import (
"fmt"
"strconv"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/sirupsen/logrus"
)
func nodeSync() (err error) {
db := database.GetDatabase()
defer db.Close()
appId := ""
facets := []string{}
if node.Self.UserDomain != "" {
domain := node.Self.UserDomain
port := node.Self.Port
if node.Self.Protocol == "https" && port != 443 {
domain += ":" + strconv.Itoa(port)
}
appId = fmt.Sprintf("https://%s/auth/u2f/app.json", domain)
}
nodes, err := node.GetAll(db)
if err != nil {
return
}
domains := set.NewSet()
for _, nde := range nodes {
if appId == "" {
appId = fmt.Sprintf("https://%s/auth/u2f/app.json",
nde.UserDomain)
}
domain := nde.UserDomain
port := nde.Port
if domain != "" {
if nde.Protocol == "https" && port != 443 {
domain += ":" + strconv.Itoa(port)
}
if !domains.Contains(domain) {
domains.Add(domain)
facets = append(facets, fmt.Sprintf("https://%s", domain))
}
}
domain = nde.AdminDomain
port = nde.Port
if domain != "" {
if nde.Protocol == "https" && port != 443 {
domain += ":" + strconv.Itoa(port)
}
if !domains.Contains(domain) {
domains.Add(domain)
facets = append(facets, fmt.Sprintf("https://%s", domain))
}
}
}
settings.Local.AppId = appId
settings.Local.Facets = facets
return
}
func nodeRunner() {
time.Sleep(1 * time.Second)
for {
if constants.Shutdown {
return
}
err := nodeSync()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("sync: Failed to sync node status")
}
time.Sleep(10 * time.Second)
}
}
func initNode() {
go nodeRunner()
}
================================================
FILE: sync/sync.go
================================================
package sync
func Init() {
initAuth()
initNode()
initVm()
}
================================================
FILE: sync/vm.go
================================================
package sync
import (
"runtime/debug"
"time"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deploy"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/iptables"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/state"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/sirupsen/logrus"
)
func deployState(runtimes *state.Runtimes) (err error) {
defer func() {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("sync: Panic in state deploy")
}
}()
start := time.Now()
stat, err := state.GetState(runtimes)
if err != nil {
return
}
err = deploy.Deploy(stat, runtimes)
if err != nil {
return
}
runtimes.Total = time.Since(start)
return
}
func syncNodeFirewall() {
defer func() {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("sync: Panic in node firewall")
}
}()
db := database.GetDatabase()
defer db.Close()
if !node.Self.Firewall {
iptables.UpdateState(node.Self, []*vpc.Vpc{}, []*instance.Instance{},
[]string{}, nil, map[string][]*firewall.Rule{},
map[string][]*firewall.Mapping{})
return
}
for i := 0; i < 2; i++ {
fires, err := firewall.GetRoles(db, node.Self.Roles)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("sync: Failed to get node firewall rules")
return
}
ingress := firewall.MergeIngress(fires)
iptables.UpdateStateRecover(node.Self, []*vpc.Vpc{},
[]*instance.Instance{}, []string{}, ingress,
map[string][]*firewall.Rule{}, map[string][]*firewall.Mapping{})
break
}
}
func vmRunner() {
time.Sleep(1 * time.Second)
for {
time.Sleep(1 * time.Second)
if constants.Shutdown {
return
}
if !node.Self.IsHypervisor() {
syncNodeFirewall()
continue
}
break
}
logrus.WithFields(logrus.Fields{
"production": constants.Production,
}).Info("sync: Starting hypervisor")
runtimes := &state.Runtimes{}
runtimes.Init()
for {
if runtimes.Total > 1500*time.Millisecond {
runtimes.Log()
}
delay := (3000 * time.Millisecond) - runtimes.Total
if delay < 50*time.Millisecond {
delay = 50 * time.Millisecond
}
time.Sleep(delay)
runtimes = &state.Runtimes{}
runtimes.Init()
if constants.Shutdown {
return
}
if !node.Self.IsHypervisor() {
syncNodeFirewall()
continue
}
err := deployState(runtimes)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("sync: Failed to deploy state")
continue
}
}
}
func initVm() {
go vmRunner()
}
================================================
FILE: systemd/systemd.go
================================================
package systemd
import (
"strings"
"sync"
"time"
"github.com/pritunl/pritunl-cloud/utils"
)
var (
systemdLock = sync.Mutex{}
)
func Reload() (err error) {
systemdLock.Lock()
defer systemdLock.Unlock()
_, err = utils.ExecCombinedOutput("", "systemctl", "daemon-reload")
if err != nil {
return
}
time.Sleep(100 * time.Millisecond)
return
}
func Start(unit string) (err error) {
systemdLock.Lock()
defer systemdLock.Unlock()
_, err = utils.ExecCombinedOutputLogged(nil, "systemctl", "start", unit)
if err != nil {
return
}
time.Sleep(300 * time.Millisecond)
return
}
func Restart(unit string) (err error) {
systemdLock.Lock()
defer systemdLock.Unlock()
_, err = utils.ExecCombinedOutputLogged(nil, "systemctl", "restart", unit)
if err != nil {
return
}
time.Sleep(300 * time.Millisecond)
return
}
func Stop(unit string) (err error) {
systemdLock.Lock()
defer systemdLock.Unlock()
_, err = utils.ExecCombinedOutput("", "systemctl", "stop", unit)
if err != nil {
return
}
time.Sleep(300 * time.Millisecond)
return
}
func GetState(unit string) (state string, timestamp time.Time, err error) {
systemdLock.Lock()
defer systemdLock.Unlock()
output, _ := utils.ExecOutput("", "systemctl", "show",
"--no-page", unit)
timestampStr := ""
exitCode := ""
exitStatus := ""
for _, line := range strings.Split(output, "\n") {
n := len(line)
if state == "" && n > 13 && line[:12] == "ActiveState=" {
state = line[12:]
} else if exitCode == "" && n > 13 && line[:13] == "ExecMainCode=" {
exitCode = line[13:]
} else if exitStatus == "" && n > 15 &&
line[:15] == "ExecMainStatus=" {
exitStatus = line[15:]
} else if timestampStr == "" && n > 24 &&
line[:23] == "ExecMainStartTimestamp=" {
timestampStr = line[23:]
}
}
if (state == "failed" && exitCode == "2" && exitStatus == "31") ||
(state == "failed" && exitCode == "3" && exitStatus == "31") {
state = "inactive"
}
if timestampStr != "" && timestampStr != "0" && timestampStr != "n/a" {
timestamp, _ = time.Parse("Mon 2006-01-02 15:04:05 MST", timestampStr)
}
return
}
================================================
FILE: systemd/utils.go
================================================
package systemd
import (
"fmt"
"time"
)
func FormatUptime(timestamp time.Time) (uptime string) {
since := time.Since(timestamp)
minutes := int64(since.Minutes())
days := minutes / 1440
hours := (minutes % 1440) / 60
minutes = (minutes % 1440) % 60
if days > 0 {
uptime = fmt.Sprintf("%d days", days)
}
if hours > 0 || uptime != "" {
if uptime != "" {
uptime += " "
}
uptime += fmt.Sprintf("%d hours", hours)
}
if uptime != "" {
uptime += " "
}
uptime += fmt.Sprintf("%d mins", minutes)
return
}
func FormatUptimeShort(timestamp time.Time) (uptime string) {
since := time.Since(timestamp)
minutes := int64(since.Minutes())
days := minutes / 1440
hours := (minutes % 1440) / 60
minutes = (minutes % 1440) % 60
if days > 3 {
uptime = fmt.Sprintf("%d days", days)
} else {
if days > 0 {
hours += days * 24
}
if hours > 0 {
uptime += fmt.Sprintf("%d hr", hours)
}
if minutes > 0 {
if uptime != "" {
uptime += " "
}
uptime += fmt.Sprintf("%d mn", minutes)
}
}
return
}
================================================
FILE: task/acme.go
================================================
package task
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/acme"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/sirupsen/logrus"
)
var acmeRenew = &Task{
Name: "acme_renew",
Version: 1,
Hours: []int{7},
Minutes: []int{45},
Handler: acmeRenewHandler,
}
func acmeRenewHandler(db *database.Database) (err error) {
certs, err := certificate.GetAll(db, &bson.M{})
if err != nil {
return
}
for _, cert := range certs {
if cert.Type != certificate.LetsEncrypt {
continue
}
err = acme.Renew(db, cert, false)
if err != nil {
logrus.WithFields(logrus.Fields{
"certificate_id": cert.Id.Hex(),
"certificate_name": cert.Name,
"error": err,
}).Error("task: Failed to renew certificate")
continue
}
}
return
}
func init() {
register(acmeRenew)
}
================================================
FILE: task/advisory.go
================================================
package task
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/advisory"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/sirupsen/logrus"
)
var advisoryData = &Task{
Name: "advisory",
Version: 1,
Hours: []int{0, 3, 6, 9, 12, 15, 18, 21},
Minutes: []int{22},
Handler: advisoryDataHandler,
RunOnStart: true,
}
func advisoryDataHandler(db *database.Database) (err error) {
advisories := map[string]*advisory.Advisory{}
coll := db.Instances()
cursor, err := coll.Find(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
inst := &instance.Instance{}
err = cursor.Decode(inst)
if err != nil {
err = database.ParseError(err)
return
}
if inst.Guest == nil {
continue
}
for _, updt := range inst.Guest.Updates {
details := []*advisory.Advisory{}
for _, cve := range updt.Cves {
adv, ok := advisories[cve]
if !ok {
adv, err = advisory.GetOneLimit(db, cve)
if err != nil {
logrus.WithFields(logrus.Fields{
"cve_id": cve,
"error": err,
}).Error("task: Failed to query CVE")
err = nil
adv = nil
}
advisories[cve] = adv
}
if adv != nil {
details = append(details, adv)
}
}
updt.Details = details
}
err = inst.CommitFields(db, set.NewSet("guest"))
if err != nil {
return
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func init() {
register(advisoryData)
}
================================================
FILE: task/backing.go
================================================
package task
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var backingClean = &Task{
Name: "backing_clean",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},
Handler: backingCleanHandler,
}
func backingCleanHandler(db *database.Database) (err error) {
backingDir := paths.GetBackingPath()
diskKeys, err := disk.GetAllKeys(db, node.Self.Id)
if err != nil {
return
}
exists, err := utils.ExistsDir(backingDir)
if !exists {
return
}
items, err := ioutil.ReadDir(backingDir)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "task: Failed to read backing directory"),
}
return
}
for _, item := range items {
name := item.Name()
pth := filepath.Join(backingDir, name)
if strings.HasPrefix(name, "image-") {
keys := strings.Split(name, "-")
if len(keys) != 3 {
continue
}
key := fmt.Sprintf("%s-%s", keys[1], keys[2])
if !diskKeys.Contains(key) {
if time.Since(item.ModTime()) > 5*time.Minute {
logrus.WithFields(logrus.Fields{
"key": key,
"path": pth,
}).Info("task: Removing unused backing image")
os.Remove(pth)
continue
}
}
}
}
return
}
func init() {
register(backingClean)
}
================================================
FILE: task/balancer.go
================================================
package task
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/database"
)
var balancerClean = &Task{
Name: "balancer_clean",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{35},
Handler: balancerCleanHandler,
}
func balancerCleanHandler(db *database.Database) (err error) {
balcns, err := balancer.GetAll(db, &bson.M{})
for _, balnc := range balcns {
err = balnc.Clean(db)
if err != nil {
return
}
}
return
}
func init() {
register(balancerClean)
}
================================================
FILE: task/blocks.go
================================================
package task
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var blocksCheck = &Task{
Name: "blocks_check",
Version: 1,
Hours: []int{7},
Minutes: []int{30},
Handler: blocksCheckHandler,
}
func blocksCheckHandler(db *database.Database) (err error) {
coll := db.Blocks()
ipColl := db.BlocksIp()
instColl := db.Instances()
ipBlocks := []bson.ObjectID{}
err = ipColl.Distinct(db, "block", &bson.M{
"type": &bson.M{
"$in": []string{
block.External,
block.IPv4,
block.IPv6,
},
},
}).Decode(&ipBlocks)
if err != nil {
err = database.ParseError(err)
return
}
blocks := set.NewSet()
ipBlocksSet := set.NewSet()
for _, ipBlock := range ipBlocks {
ipBlocksSet.Add(ipBlock)
}
cursor, err := coll.Find(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
blck := &block.Block{}
err = cursor.Decode(blck)
if err != nil {
err = database.ParseError(err)
return
}
blocks.Add(blck.Id)
err = blck.ValidateAddresses(db, nil)
if err != nil {
return
}
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
ipBlocksSet.Subtract(blocks)
for blckIdInf := range ipBlocksSet.Iter() {
blckId := blckIdInf.(bson.ObjectID)
cursor2, e := ipColl.Find(db, &bson.M{
"block": blckId,
})
if e != nil {
err = database.ParseError(e)
return
}
defer cursor2.Close(db)
for cursor2.Next(db) {
blckIp := &block.BlockIp{}
err = cursor2.Decode(blckIp)
if err != nil {
err = database.ParseError(err)
return
}
logrus.WithFields(logrus.Fields{
"ip_address": utils.Int2IpAddress(blckIp.Ip).String(),
"block_id": blckIp.Id.Hex(),
"instance_id": blckIp.Instance.Hex(),
}).Warn("task: Removing lost block IP")
_, _ = instColl.UpdateOne(db, &bson.M{
"_id": blckIp.Instance,
}, &bson.M{
"$set": &bson.M{
"restart_block_ip": true,
},
})
_, err = ipColl.DeleteOne(db, &bson.M{
"_id": blckIp.Id,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
}
err = cursor2.Err()
if err != nil {
err = database.ParseError(err)
return
}
}
return
}
func init() {
register(blocksCheck)
}
================================================
FILE: task/cache.go
================================================
package task
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var cacheClean = &Task{
Name: "cache_clean",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},
Handler: cacheCleanHandler,
}
func cacheCleanHandler(db *database.Database) (err error) {
cacheDir := node.Self.GetCachePath()
imageKeys, err := image.GetAllKeys(db)
if err != nil {
return
}
exists, err := utils.ExistsDir(cacheDir)
if !exists {
return
}
items, err := ioutil.ReadDir(cacheDir)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "task: Failed to read cache directory"),
}
return
}
for _, item := range items {
name := item.Name()
pth := filepath.Join(cacheDir, name)
if strings.HasPrefix(name, "image-") {
keys := strings.Split(name, "-")
if len(keys) != 3 {
logrus.WithFields(logrus.Fields{
"path": pth,
}).Warning("task: Removing unknown image cache")
os.Remove(pth)
continue
}
key := fmt.Sprintf("%s-%s", keys[1], keys[2])
if !imageKeys.Contains(key) {
if time.Since(item.ModTime()) > 5*time.Minute {
logrus.WithFields(logrus.Fields{
"key": key,
"path": pth,
}).Info("task: Removing old image cache")
os.Remove(pth)
continue
}
}
}
}
return
}
func init() {
register(cacheClean)
}
================================================
FILE: task/constants.go
================================================
package task
const (
Running = "running"
Failed = "failed"
Finished = "finished"
)
var (
AllHours = []int{
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
}
TwoHours = []int{
0,
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
22,
}
SixHours = []int{
0,
6,
12,
18,
}
AllMins = []int{
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
33,
34,
35,
36,
37,
38,
39,
40,
41,
42,
43,
44,
45,
46,
47,
48,
49,
50,
51,
52,
53,
54,
55,
56,
57,
58,
59,
}
TwoMins = []int{
0,
2,
4,
6,
8,
10,
12,
14,
16,
18,
20,
22,
24,
26,
28,
30,
32,
34,
36,
38,
40,
42,
44,
46,
48,
50,
52,
54,
56,
58,
}
FiveMins = []int{
0,
5,
10,
15,
20,
25,
30,
35,
40,
45,
50,
55,
}
TenMins = []int{
0,
10,
20,
30,
40,
50,
}
FifteenMins = []int{
0,
15,
30,
45,
}
ThirtyMins = []int{
0,
30,
}
)
================================================
FILE: task/deployments.go
================================================
package task
import (
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/planner"
)
var deployments = &Task{
Name: "deployments",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59},
Seconds: 3 * time.Second,
Handler: deploymentsHandler,
}
func deploymentsHandler(db *database.Database) (err error) {
plnr := &planner.Planner{}
err = plnr.ApplyPlans(db)
if err != nil {
return
}
return
}
func init() {
register(deployments)
}
================================================
FILE: task/domains.go
================================================
package task
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/settings"
)
var domains = &Task{
Name: "domains",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59},
Handler: domainsHandler,
}
func domainsHandler(db *database.Database) (err error) {
refreshTtl := time.Duration(
settings.System.DomainRefreshTtl) * time.Second
domns, err := domain.GetAll(db, &bson.M{
"last_update": &bson.M{
"$gte": time.Now().Add(-refreshTtl),
},
})
if err != nil {
return
}
for _, domn := range domns {
domain.Refresh(db, domn.Id)
}
return
}
func init() {
register(domains)
}
================================================
FILE: task/imds.go
================================================
package task
import (
"math/rand"
"sync"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/imds"
"github.com/pritunl/pritunl-cloud/imds/types"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/sirupsen/logrus"
)
var imdsSync = &Task{
Name: "imds_sync",
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59},
Seconds: 3 * time.Second,
Local: true,
Handler: imdsSyncHandler,
}
var (
failTime = map[bson.ObjectID]failTimeData{}
)
type failTimeData struct {
timestamp time.Time
logged bool
}
func test() {
test := []int{1, 2, 3, 4, 5, 6, 7, 8, 9}
for _, val := range test {
go func() {
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond)
print(val)
}()
}
}
func imdsSyncHandler(db *database.Database) (err error) {
confs := imds.GetConfigs()
logTtl := time.Duration(
settings.Hypervisor.ImdsSyncLogTimeout) * time.Second
restartTtl := time.Duration(
settings.Hypervisor.ImdsSyncRestartTimeout) * time.Second
newFailTime := map[bson.ObjectID]failTimeData{}
newFailTimeLock := sync.Mutex{}
waiter := &sync.WaitGroup{}
for _, conf := range confs {
if conf.Instance == nil || conf.Instance.NetworkNamespace == "" {
continue
}
waiter.Add(1)
go func(conf *types.Config) {
defer waiter.Done()
err := imds.Sync(db, conf.Instance.NetworkNamespace,
conf.Instance.Id, conf.Instance.Deployment, conf)
if err != nil {
newFailTimeLock.Lock()
ttlData := failTime[conf.Instance.Id]
if ttlData.timestamp.IsZero() {
newFailTime[conf.Instance.Id] = failTimeData{
timestamp: time.Now(),
}
} else if time.Since(ttlData.timestamp) > logTtl &&
!ttlData.logged {
logrus.WithFields(logrus.Fields{
"action": conf.Instance.Action,
"instance": conf.Instance.Id.Hex(),
"error": err,
}).Error("task: Failed to sync imds")
newFailTime[conf.Instance.Id] = failTimeData{
timestamp: ttlData.timestamp,
logged: true,
}
} else if time.Since(ttlData.timestamp) > restartTtl {
logrus.WithFields(logrus.Fields{
"action": conf.Instance.Action,
"instance": conf.Instance.Id.Hex(),
"error": err,
}).Error("task: Failed to sync imds, restarting...")
e := imds.Restart(conf.Instance.Id)
if e != nil {
logrus.WithFields(logrus.Fields{
"action": conf.Instance.Action,
"instance": conf.Instance.Id.Hex(),
"error": e,
}).Error("task: Failed to restart imds")
}
} else {
newFailTime[conf.Instance.Id] = failTime[conf.Instance.Id]
}
newFailTimeLock.Unlock()
}
}(conf)
}
waiter.Wait()
failTime = newFailTime
return
}
func init() {
register(imdsSync)
}
================================================
FILE: task/job.go
================================================
package task
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
type Job struct {
Id string `bson:"_id"`
Name string `bson:"name"`
State string `bson:"state"`
Retry bool `bson:"retry"`
Node bson.ObjectID `bson:"node"`
Timestamp time.Time `bson:"timestamp"`
}
func (j *Job) Reserve(db *database.Database) (reserved bool, err error) {
coll := db.Tasks()
_, err = coll.InsertOne(db, j)
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.DuplicateKeyError:
err = nil
break
}
return
}
reserved = true
return
}
func (j *Job) Failed(db *database.Database) (err error) {
coll := db.Tasks()
err = coll.UpdateId(j.Id, &bson.M{
"$set": &bson.M{
"state": Failed,
},
})
return
}
func (j *Job) Finished(db *database.Database) (err error) {
coll := db.Tasks()
err = coll.UpdateId(j.Id, &bson.M{
"$set": &bson.M{
"state": Finished,
},
})
return
}
================================================
FILE: task/notification.go
================================================
package task
import (
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/notification"
"github.com/sirupsen/logrus"
)
var notificationCheck = &Task{
Name: "notification_check",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{15},
Handler: notificationCheckHandler,
RunOnStart: true,
}
func notificationCheckHandler(db *database.Database) (err error) {
err = notification.Check()
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("task: Failed to check vulnerability alerts")
}
return
}
func init() {
register(notificationCheck)
}
================================================
FILE: task/scheduler.go
================================================
package task
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/scheduler"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/sirupsen/logrus"
)
var schedule = &Task{
Name: "schedule",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59},
Seconds: 5 * time.Second,
Handler: scheduleHandler,
}
func scheduleUnits(db *database.Database) (err error) {
units, err := unit.GetAll(db, &bson.M{
"kind": bson.M{
"$in": []string{deployment.Instance, deployment.Image},
},
})
if err != nil {
return
}
deploymentIds, err := deployment.GetAllActiveIds(db)
if err != nil {
return
}
for _, unt := range units {
for _, deplyId := range unt.Deployments {
if !deploymentIds.Contains(deplyId) {
logrus.WithFields(logrus.Fields{
"pod": unt.Pod.Hex(),
"unit": unt.Id.Hex(),
"deployment": deplyId.Hex(),
}).Info("deploy: Removing deployment")
err = unt.RemoveDeployement(db, deplyId)
if err != nil {
return
}
}
}
}
for _, unt := range units {
if len(unt.Deployments) >= unt.Count {
continue
}
err = scheduler.Schedule(db, unt)
if err != nil {
return
}
}
return
}
func scheduleHandler(db *database.Database) (err error) {
err = scheduleUnits(db)
if err != nil {
return
}
schds, err := scheduler.GetAll(db)
if err != nil {
return
}
for _, schd := range schds {
if schd.Consumed >= schd.Count {
_, err = scheduler.Remove(db, schd.Id)
if err != nil {
return
}
}
}
return
}
func init() {
register(schedule)
}
================================================
FILE: task/spec.go
================================================
package task
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/sirupsen/logrus"
)
var specs = &Task{
Name: "specs",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55},
Handler: specsHandler,
}
func specsHandler(db *database.Database) (err error) {
deplys, err := deployment.GetAll(db, &bson.M{})
if err != nil {
return
}
specIdsSet := set.NewSet()
for _, deply := range deplys {
if deply.Kind != deployment.Instance {
continue
}
specIdsSet.Add(deply.Spec)
}
specIds := []bson.ObjectID{}
for specId := range specIdsSet.Iter() {
specIds = append(specIds, specId.(bson.ObjectID))
}
specs, err := spec.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": specIds,
},
})
if err != nil {
return
}
for _, spec := range specs {
errData, e := spec.Refresh(db)
if e != nil || errData != nil {
err = e
logrus.WithFields(logrus.Fields{
"spec_id": spec.Id.Hex(),
"error": err,
"error_data": errData,
}).Error("deploy: Failed to refresh active spec")
err = nil
errData = nil
}
}
return
}
func init() {
register(specs)
}
================================================
FILE: task/specindex.go
================================================
package task
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
)
var specIndex = &Task{
Name: "spec_index",
Version: 1,
Hours: []int{6},
Minutes: []int{32},
Handler: specIndexHandler,
}
func specIndexSyncUnit(db *database.Database, unt *unit.Unit) (err error) {
specs, err := spec.GetAllIndexes(db, &bson.M{
"unit": unt.Id,
})
index := 0
for i, spc := range specs {
index = i + 1
if spc.Index != index {
spc.Index = index
err = spc.CommitFields(db, set.NewSet("index"))
if err != nil {
return
}
}
}
if unt.SpecIndex != index {
unt.SpecIndex = index
err = unt.CommitFields(db, set.NewSet("spec_index"))
if err != nil {
return
}
}
return
}
func specIndexHandler(db *database.Database) (err error) {
units, err := unit.GetAll(db, &bson.M{})
if err != nil {
return
}
for _, unt := range units {
err = specIndexSyncUnit(db, unt)
if err != nil {
return
}
}
return
}
func init() {
register(specIndex)
}
================================================
FILE: task/storage.go
================================================
package task
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/storage"
"github.com/sirupsen/logrus"
)
var storageSync = &Task{
Name: "storage_renew",
Version: 1,
Hours: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23},
Minutes: []int{0, 14, 29, 44, 59},
Handler: storageSyncHandler,
RunOnStart: true,
}
func storageSyncHandler(db *database.Database) (err error) {
coll := db.Images()
imgStoreIdsList := []bson.ObjectID{}
err = coll.Distinct(
db,
"storage",
&bson.M{},
).Decode(&imgStoreIdsList)
if err != nil {
err = database.ParseError(err)
return
}
imgStoreIds := set.NewSet()
for _, storeId := range imgStoreIdsList {
imgStoreIds.Add(storeId)
}
storeIds := set.NewSet()
stores, err := storage.GetAll(db)
if err != nil {
return
}
for _, store := range stores {
storeIds.Add(store.Id)
err = data.Sync(db, store)
if err != nil {
logrus.WithFields(logrus.Fields{
"storage_id": store.Id.Hex(),
"storage_name": store.Name,
"error": err,
}).Error("task: Failed to sync storage")
}
}
imgStoreIds.Subtract(storeIds)
remStoreIds := []bson.ObjectID{}
for storeIdInf := range imgStoreIds.Iter() {
storeId := storeIdInf.(bson.ObjectID)
logrus.WithFields(logrus.Fields{
"storage_id": storeId.Hex(),
}).Warning("task: Cleaning unknown images")
remStoreIds = append(remStoreIds, storeId)
}
if len(remStoreIds) > 0 {
_, err = coll.DeleteMany(db, &bson.M{
"storage": &bson.M{
"$in": remStoreIds,
},
})
if err != nil {
return
}
}
event.PublishDispatch(db, "image.change")
return
}
func init() {
register(storageSync)
}
================================================
FILE: task/task.go
================================================
package task
import (
"fmt"
"runtime/debug"
"time"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/version"
"github.com/sirupsen/logrus"
)
var (
registry = []*Task{}
)
type Task struct {
Name string
Version int
Hours []int
Minutes []int
Seconds time.Duration
Retry bool
Handler func(*database.Database) error
RunOnStart bool
Local bool
DebugNodes []string
timestamp time.Time
}
func (t *Task) scheduled(hour, min int) bool {
for _, h := range t.Hours {
if h == hour {
for _, m := range t.Minutes {
if m == min {
return true
}
}
}
}
return false
}
func (t *Task) runShared(db *database.Database, now time.Time) {
defer func() {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("sync: Panic in run task")
}
}()
if t.Seconds == 0 {
time.Sleep(time.Duration(utils.RandInt(0, 1000)) * time.Millisecond)
} else {
time.Sleep(time.Duration(utils.RandInt(0, 300)) * time.Millisecond)
}
if t.DebugNodes != nil {
matched := false
for _, ndeName := range t.DebugNodes {
if node.Self.Name == ndeName {
matched = true
}
}
if !matched {
return
}
}
id := fmt.Sprintf("%s-%d", t.Name, now.Unix()-int64(now.Second()))
if t.Seconds != 0 {
id += fmt.Sprintf("-%d", GetBlock(now, t.Seconds))
}
job := &Job{
Id: id,
Name: t.Name,
State: Running,
Retry: t.Retry,
Node: node.Self.Id,
Timestamp: time.Now(),
}
reserved, err := job.Reserve(db)
if err != nil {
logrus.WithFields(logrus.Fields{
"task": t.Name,
"error": err,
}).Error("task: Task reserve failed")
return
}
if !reserved {
return
}
err = t.Handler(db)
if err != nil {
logrus.WithFields(logrus.Fields{
"task": t.Name,
"error": err,
}).Error("task: Task failed")
_ = job.Failed(db)
return
}
_ = job.Finished(db)
}
func (t *Task) runLocal(db *database.Database, now time.Time) {
defer func() {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("sync: Panic in run local task")
}
}()
if t.DebugNodes != nil {
matched := false
for _, ndeName := range t.DebugNodes {
if node.Self.Name == ndeName {
matched = true
}
}
if !matched {
return
}
}
id := fmt.Sprintf("%s-%d", t.Name, now.Unix()-int64(now.Second()))
if t.Seconds != 0 {
id += fmt.Sprintf("-%d", GetBlock(now, t.Seconds))
}
err := t.Handler(db)
if err != nil {
logrus.WithFields(logrus.Fields{
"task": t.Name,
"error": err,
}).Error("task: Local task failed")
return
}
}
func (t *Task) run(now time.Time) {
go func() {
db := database.GetDatabase()
defer db.Close()
if t.Version != 0 {
supported, err := version.Check(db, t.Name, t.Version)
if err != nil {
logrus.WithFields(logrus.Fields{
"task": t.Name,
"error": err,
}).Error("task: Version check failed")
return
}
if !supported {
logrus.WithFields(logrus.Fields{
"task": t.Name,
"version": t.Version,
}).Info("task: Skipping incompatible task")
return
}
}
curTimestamp := t.timestamp
if !curTimestamp.IsZero() {
if time.Since(curTimestamp) > 10*time.Minute {
logrus.WithFields(logrus.Fields{
"task_name": t.Name,
"runtime": time.Since(curTimestamp),
}).Error("task: Task stuck running")
}
return
}
t.timestamp = time.Now()
defer func() {
t.timestamp = time.Time{}
}()
if t.Local {
t.runLocal(db, now)
} else {
t.runShared(db, now)
}
}()
}
func runScheduler() {
now := time.Now()
curHour := now.Hour()
curMin := now.Minute()
curSecBlocks := map[time.Duration]int{}
for _, task := range registry {
if task.Seconds != 0 {
curSecBlocks[task.Seconds] = GetBlock(now, task.Seconds)
}
if task.RunOnStart {
go task.run(now)
}
}
for {
time.Sleep(1 * time.Second)
now = time.Now()
hour := now.Hour()
min := now.Minute()
for block, curSecBlock := range curSecBlocks {
secBlock := GetBlock(now, block)
if curSecBlock != secBlock {
for _, task := range registry {
if task.Seconds != 0 && task.Seconds == block &&
task.scheduled(hour, min) {
task.run(now)
}
}
}
curSecBlocks[block] = secBlock
}
if curHour == hour && curMin == min {
continue
}
curHour = hour
curMin = min
for _, task := range registry {
if task.Seconds == 0 && task.scheduled(hour, min) {
task.run(now)
}
}
}
}
func register(task *Task) {
registry = append(registry, task)
}
func Init() (err error) {
for _, task := range registry {
if task.Version == 0 {
continue
}
err = version.Set(database.GetDatabase(), task.Name, task.Version)
if err != nil {
logrus.WithFields(logrus.Fields{
"task": task.Name,
"version": task.Version,
"error": err,
}).Error("task: Failed to set task version")
return
}
}
go runScheduler()
return
}
func GetBlock(n time.Time, d time.Duration) int {
s := int(d.Seconds())
return (n.Second() / s) * s
}
================================================
FILE: telemetry/constants.go
================================================
package telemetry
const (
Moderate = "moderate"
Important = "important"
Critical = "critical"
)
================================================
FILE: telemetry/telemetry.go
================================================
package telemetry
import (
"fmt"
"sync"
"time"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
var (
registry []handler
)
type handler interface {
getName() string
Refresh() error
}
type Telemetry[Data any] struct {
name string
Default Data
lock sync.Mutex
lastTransmit time.Time
TransmitRate time.Duration
lastRefresh time.Time
RefreshRate time.Duration
Refresher func() (Data, error)
Validate func(Data) Data
data Data
}
func (r *Telemetry[Data]) getName() string {
return r.name
}
func (r *Telemetry[Data]) Refresh() (err error) {
r.lock.Lock()
lastRefresh := r.lastRefresh
r.lock.Unlock()
if time.Since(lastRefresh) < r.RefreshRate {
return
}
var data Data
func() {
defer utils.RecoverLog("telemetry: Panic in refresh")
data, err = r.Refresher()
if err != nil {
return
}
}()
r.Set(data)
return
}
func (r *Telemetry[Data]) Set(data Data) {
r.lock.Lock()
r.data = data
r.lastRefresh = time.Now()
r.lock.Unlock()
}
func (r *Telemetry[Data]) Get() (Data, bool) {
r.lock.Lock()
lastRefresh := r.lastRefresh
lastTransmit := r.lastTransmit
r.lock.Unlock()
if lastRefresh.IsZero() || time.Since(lastTransmit) < r.TransmitRate {
var x Data
return x, false
}
r.lock.Lock()
r.lastTransmit = time.Now()
r.lock.Unlock()
if r.Validate != nil {
return r.Validate(r.data), true
} else {
return r.data, true
}
}
func Register[Data any](telm *Telemetry[Data]) {
telm.name = fmt.Sprintf("%T", telm)
registry = append(registry, telm)
}
func Refresh() {
for _, telm := range registry {
err := telm.Refresh()
if err != nil {
logrus.WithFields(logrus.Fields{
"kind": telm.getName(),
}).Error("telemetry: Telemetry refresh failed")
}
}
}
================================================
FILE: telemetry/updates.go
================================================
package telemetry
import (
"regexp"
"sort"
"strings"
"time"
"github.com/pritunl/pritunl-cloud/advisory"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
var (
cveReg = regexp.MustCompile(`CVE-\d{4}-\d+`)
)
var Updates = &Telemetry[[]*Update]{
TransmitRate: 6 * time.Minute,
RefreshRate: 6 * time.Hour,
Refresher: UpdatesRefresh,
Validate: func(data []*Update) []*Update {
if len(data) > 50 {
return data[:50]
}
return data
},
}
type Update struct {
Advisory string `bson:"advisory" json:"advisory"`
Cves []string `bson:"cves" json:"cves"`
Severity string `bson:"severity" json:"severity"`
Description string `bson:"description" json:"description"`
Packages []string `bson:"packages" json:"packages"`
Details []*advisory.Advisory `bson:"details" json:"details"`
}
func parseRecord(lines []string) (update *Update) {
updt := &Update{}
descLines := []string{}
currentField := ""
for _, line := range lines {
colonIdx := strings.Index(line, ":")
if colonIdx < 0 {
continue
}
prefix := line[:colonIdx]
value := strings.TrimSpace(line[colonIdx+1:])
if strings.TrimSpace(prefix) == "" {
switch currentField {
case "Description":
descLines = append(descLines, value)
case "CVEs":
if value != "" {
updt.Cves = append(updt.Cves,
cveReg.FindAllString(value, -1)...)
}
}
continue
}
field := strings.TrimSpace(prefix)
currentField = field
switch field {
case "Update ID", "Name":
updt.Advisory = value
case "Severity":
updt.Severity = parseSeverity(value)
case "Description":
if value != "" {
descLines = append(descLines, value)
}
case "CVEs":
if value != "" {
updt.Cves = append(updt.Cves,
cveReg.FindAllString(value, -1)...)
}
}
}
if !matchAdvisory(updt.Advisory) {
return
}
if updt.Severity == "" {
return
}
for len(descLines) > 0 && descLines[len(descLines)-1] == "" {
descLines = descLines[:len(descLines)-1]
}
updt.Description = strings.Join(descLines, "\n")
fullText := strings.Join(lines, "\n")
cveSet := map[string]bool{}
deduped := []string{}
for _, c := range updt.Cves {
if !cveSet[c] {
cveSet[c] = true
deduped = append(deduped, c)
}
}
for _, c := range cveReg.FindAllString(fullText, -1) {
if !cveSet[c] {
cveSet[c] = true
deduped = append(deduped, c)
}
}
sort.Strings(deduped)
updt.Cves = deduped
update = updt
return
}
func updatesList() (advisories map[string][]string, err error) {
if !IsDnf() {
return
}
resp, err := commander.Exec(&commander.Opt{
Name: "dnf",
Args: []string{
"updateinfo",
"list",
},
Timeout: 90 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
if resp != nil {
logrus.WithFields(
resp.Map(),
).Error("telemetry: Failed to get dnf security update list")
}
return
}
advisories = map[string][]string{}
seen := map[string]map[string]bool{}
for _, line := range strings.Split(string(resp.Output), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "Last metadata") ||
strings.HasPrefix(line, "Updating") ||
strings.HasPrefix(line, "Repositories") {
continue
}
parts := strings.Fields(line)
if len(parts) < 3 {
continue
}
adv := parts[0]
if !matchAdvisory(adv) {
continue
}
pkg := ""
part1 := strings.ToLower(parts[1])
part2 := strings.ToLower(parts[2])
if strings.Contains(part1, Moderate) ||
strings.Contains(part1, Important) ||
strings.Contains(part1, Critical) {
pkg = parts[2]
} else if len(parts) >= 4 && (strings.Contains(part2, Moderate) ||
strings.Contains(part2, Important) ||
strings.Contains(part2, Critical)) {
pkg = parts[3]
} else {
continue
}
if pkg == "" {
continue
}
pkgSet, ok := seen[adv]
if !ok {
pkgSet = map[string]bool{}
seen[adv] = pkgSet
}
if !pkgSet[pkg] {
pkgSet[pkg] = true
advisories[adv] = append(advisories[adv], pkg)
}
}
for adv := range advisories {
sort.Strings(advisories[adv])
}
return
}
func UpdatesRefresh() (updates []*Update, err error) {
if !IsDnf() {
return
}
pkgMap, err := updatesList()
if err != nil {
return
}
resp, err := commander.Exec(&commander.Opt{
Name: "dnf",
Args: []string{
"updateinfo",
"info",
},
Timeout: 120 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
if resp != nil {
logrus.WithFields(
resp.Map(),
).Error("telemetry: Failed to get dnf updateinfo report")
}
return
}
updates = []*Update{}
seen := map[string]bool{}
var current []string
flush := func() {
if len(current) == 0 {
return
}
record := current
current = nil
upd := parseRecord(record)
if upd == nil {
return
}
if seen[upd.Advisory] {
return
}
pkgs, ok := pkgMap[upd.Advisory]
if !ok {
return
}
seen[upd.Advisory] = true
upd.Packages = pkgs
updates = append(updates, upd)
}
for _, line := range strings.Split(string(resp.Output), "\n") {
if isSeparatorLine(line) {
flush()
continue
}
if strings.HasPrefix(
strings.ReplaceAll(line, " ", ""), "Name:") {
flush()
}
current = append(current, line)
}
flush()
return
}
func init() {
Register(Updates)
}
================================================
FILE: telemetry/utils.go
================================================
package telemetry
import (
"bytes"
"os"
"strings"
"time"
"github.com/pritunl/tools/commander"
)
var (
hasSevs = 0
)
func IsDnf() bool {
_, err := os.Stat("/usr/bin/dnf")
return err == nil
}
func HasSevs() bool {
if hasSevs == 1 {
return false
} else if hasSevs == 2 {
return true
}
resp, err := commander.Exec(&commander.Opt{
Name: "dnf",
Args: []string{
"updateinfo",
"list",
"--help",
},
Timeout: 8 * time.Second,
PipeOut: true,
PipeErr: true,
})
if err != nil {
hasSevs = 1
return false
}
if bytes.Contains(resp.Output, []byte("--advisory-severities")) {
hasSevs = 2
return true
}
return false
}
func matchAdvisory(id string) bool {
return strings.HasPrefix(id, "RHSA-") ||
strings.HasPrefix(id, "ALSA-") ||
strings.HasPrefix(id, "RLSA-") ||
strings.HasPrefix(id, "ELSA-") ||
strings.HasPrefix(id, "FEDORA-")
}
func parseSeverity(value string) string {
switch strings.ToLower(strings.TrimSpace(value)) {
case "critical":
return Critical
case "important":
return Important
case "moderate":
return Moderate
}
return ""
}
func isSeparatorLine(line string) bool {
trimmed := strings.TrimSpace(line)
if len(trimmed) == 0 {
return false
}
for _, r := range trimmed {
if r != '=' {
return false
}
}
return true
}
================================================
FILE: tools/autoindex.py
================================================
import os
import sys
from datetime import datetime, timezone
ROOT_DIR = "/mnt/images"
PREFIX = ""
if len(sys.argv) > 1:
PREFIX = sys.argv[1] + "/"
HTML_HEADER = """Index of /{relative_path}
Index of /{relative_path}
../
"""
HTML_FOOTER = """
"""
def limit_filename_length(filename, max_length=50):
if len(filename) > max_length:
return filename[:max_length - 3] + '..>'
return filename
def generate_directory_listing_html(current_dir, relative_path, dirs, files):
html = HTML_HEADER.format(relative_path=PREFIX + relative_path)
for directory in sorted(dirs):
dir_path = os.path.join(current_dir, directory)
stat_info = os.stat(dir_path)
modified_time = datetime.fromtimestamp(stat_info.st_mtime,
tz=timezone.utc).strftime("%d-%b-%Y %H:%M")
label = limit_filename_length(directory + '/')
html += f"{label}" + \
f"{' ' * (51 - len(label))}{modified_time}{' ' * 20}-\n"
for file_name in sorted(files):
if file_name == "index.html":
continue
file_path = os.path.join(current_dir, file_name)
stat_info = os.stat(file_path)
modified_time = datetime.fromtimestamp(stat_info.st_mtime,
tz=timezone.utc).strftime("%d-%b-%Y %H:%M")
file_size = f"{stat_info.st_size:21}"
label = limit_filename_length(file_name)
html += f"{label}" + \
f"{' ' * (51 - len(label))}{modified_time}{file_size}\n"
html += HTML_FOOTER
return html
def generate_index_files(root_dir):
for current_dir, subdirs, files in os.walk(root_dir):
relative_path = os.path.relpath(current_dir, root_dir)
if relative_path == ".":
relative_path = ""
else:
relative_path += "/"
html_content = generate_directory_listing_html(current_dir,
relative_path, sorted(subdirs), sorted(files))
index_path = os.path.join(current_dir, "index.html")
with open(index_path, "w") as f:
f.write(html_content)
print(f"Generated index.html for: {current_dir}")
generate_index_files(ROOT_DIR)
================================================
FILE: tools/build_run.sh
================================================
#!/bin/bash
set -e
NO_AGENT=false
STABLE=false
ARGS=()
while [[ $# -gt 0 ]]; do
case $1 in
--no-agent)
NO_AGENT=true
shift
;;
--stable)
STABLE=true
shift
;;
*)
ARGS+=("$1")
shift
;;
esac
done
build_pritunl_agent() {
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -v -o agent-static
sudo cp -f ./agent-static /usr/bin/pritunl-cloud-agent
rm -f ./agent-static
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -v -o agent-bsd
sudo cp -f ./agent-bsd /usr/bin/pritunl-cloud-agent-bsd
rm -f ./agent-bsd
}
if [ "$NO_AGENT" = false ]; then
cd agent
output=$(go install -v 2>&1 | tee /dev/tty) || exit 1
if [ -n "$output" ]; then
build_pritunl_agent
fi
cd ..
fi
cd redirect
go install -v
sudo cp -f ~/go/bin/redirect /usr/bin/pritunl-cloud-redirect
cd ..
go install -v
sudo cp -f ~/go/bin/pritunl-cloud /usr/bin/pritunl-cloud
if [ "$STABLE" = true ]; then
sudo /usr/bin/pritunl-cloud start --fast-exit
elif [ ${#ARGS[@]} -eq 0 ]; then
sudo /usr/bin/pritunl-cloud start --debug
else
sudo /usr/bin/pritunl-cloud "${ARGS[@]}"
fi
================================================
FILE: tools/builder.py
================================================
import optparse
import datetime
import re
import sys
import subprocess
import time
import math
import json
import requests
import os
import getpass
import base64
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import (
Cipher, algorithms, modes
)
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
USAGE = """Usage: builder [command] [options]
Command Help: builder [command] --help
Commands:
version Print the version and exit
set-version Set current version
build Build and release"""
CONSTANTS_PATH = 'constants/constants.go'
CHANGES_PATH = 'CHANGES'
STABLE_PACUR_PATH = '../pritunl-pacur'
TEST_PACUR_PATH = '../pritunl-pacur-test'
BUILD_KEYS_PATH = os.path.expanduser('~/data/build/pritunl_build.json')
BUILD_TARGETS = ('pritunl-cloud',)
REPO_NAME = 'pritunl-cloud'
cur_date = datetime.datetime.utcnow()
pacur_path = None
def wget(url, cwd=None, output=None):
if output:
args = ['wget', '-O', output, url]
else:
args = ['wget', url]
subprocess.check_call(args, cwd=cwd)
def post_git_asset(release_id, file_name, file_path):
file_size = os.path.getsize(file_path)
response = requests.post(
'https://uploads.github.com/repos/%s/%s/releases/%s/assets' % (
github_owner, REPO_NAME, release_id),
headers={
'Authorization': 'token %s' % github_token,
'Content-Type': 'application/octet-stream',
'Content-Size': str(file_size),
},
params={
'name': file_name,
},
data=open(file_path, 'rb').read(),
)
if response.status_code != 201:
print('Failed to create asset on github')
print(response.json())
sys.exit(1)
def get_ver(version):
day_num = (cur_date - datetime.datetime(2015, 11, 24)).days
min_num = int(math.floor(((cur_date.hour * 60) + cur_date.minute) / 14.4))
ver = re.findall(r'\d+', version)
ver_str = '.'.join((ver[0], ver[1], str(day_num), str(min_num)))
ver_str += ''.join(re.findall('[a-z]+', version))
return ver_str
def get_int_ver(version):
ver = re.findall(r'\d+', version)
if 'snapshot' in version:
pass
elif 'alpha' in version:
ver[-1] = str(int(ver[-1]) + 1000)
elif 'beta' in version:
ver[-1] = str(int(ver[-1]) + 2000)
elif 'rc' in version:
ver[-1] = str(int(ver[-1]) + 3000)
else:
ver[-1] = str(int(ver[-1]) + 4000)
return int(''.join([x.zfill(4) for x in ver]))
def iter_packages():
for target in BUILD_TARGETS:
target_path = os.path.join(pacur_path, target)
for name in os.listdir(target_path):
if cur_version not in name:
continue
elif name.endswith(".pkg.tar.zst"):
pass
elif name.endswith(".rpm"):
pass
elif name.endswith(".deb"):
pass
else:
continue
path = os.path.join(target_path, name)
yield name, path
# Parse args
if len(sys.argv) > 1:
cmd = sys.argv[1]
else:
cmd = 'version'
def aes_encrypt(passphrase, data):
enc_salt = os.urandom(32)
enc_iv = os.urandom(12)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA512(),
length=32,
salt=enc_salt,
iterations=10000000,
backend=default_backend(),
)
enc_key = kdf.derive(passphrase.encode())
cipher = Cipher(
algorithms.AES(enc_key),
modes.GCM(enc_iv),
backend=default_backend()
).encryptor()
enc_data = cipher.update(data.encode('utf-8')) + cipher.finalize()
auth_tag = cipher.tag
return '\n'.join([
base64.b64encode(enc_salt).decode('utf-8'),
base64.b64encode(enc_iv).decode('utf-8'),
base64.b64encode(enc_data).decode('utf-8'),
base64.b64encode(auth_tag).decode('utf-8'),
])
def aes_decrypt(passphrase, data):
data = data.split('\n')
if len(data) < 4:
raise ValueError('Invalid encryption data')
enc_salt = base64.b64decode(data[0])
enc_iv = base64.b64decode(data[1])
enc_data = base64.b64decode(data[2])
auth_tag = base64.b64decode(data[3])
kdf = PBKDF2HMAC(
algorithm=hashes.SHA512(),
length=32,
salt=enc_salt,
iterations=10000000,
backend=default_backend(),
)
enc_key = kdf.derive(passphrase.encode())
cipher = Cipher(
algorithms.AES(enc_key),
modes.GCM(enc_iv, auth_tag),
backend=default_backend()
).decryptor()
decrypted_data = cipher.update(enc_data) + cipher.finalize()
return decrypted_data.decode('utf-8')
passphrase = getpass.getpass('Enter passphrase: ')
if cmd == 'encrypt':
passphrase2 = getpass.getpass('Enter passphrase: ')
if passphrase != passphrase2:
print('ERROR: Passphrase mismatch')
sys.exit(1)
with open(BUILD_KEYS_PATH, 'r') as build_keys_file:
data = build_keys_file.read().strip()
enc_data = aes_encrypt(passphrase, data)
with open(BUILD_KEYS_PATH, 'w') as build_keys_file:
build_keys_file.write(enc_data)
sys.exit(0)
if cmd == 'decrypt':
with open(BUILD_KEYS_PATH, 'r') as build_keys_file:
enc_data = build_keys_file.read().strip()
data = aes_decrypt(passphrase, enc_data)
with open(BUILD_KEYS_PATH, 'w') as build_keys_file:
build_keys_file.write(data)
sys.exit(0)
# Load build keys
with open(BUILD_KEYS_PATH, 'r') as build_keys_file:
enc_data = build_keys_file.read()
data = aes_decrypt(passphrase, enc_data)
build_keys = json.loads(data.strip())
github_owner = build_keys['github_owner']
github_token = build_keys['github_token']
gitlab_token = build_keys['gitlab_token']
gitlab_host = build_keys['gitlab_host']
# Get package info
with open(CONSTANTS_PATH, 'r') as constants_file:
cur_version = re.findall('= "(.*?)"', constants_file.read())[0]
parser = optparse.OptionParser(usage=USAGE)
(options, args) = parser.parse_args()
build_num = 0
# Run cmd
if cmd == 'version':
print('%s v%s' % (REPO_NAME, cur_version))
sys.exit(0)
if cmd == 'sync-releases':
next_url = 'https://api.github.com/repos/%s/%s/releases' % (
github_owner, REPO_NAME)
while True:
# Get github release
response = requests.get(
next_url,
headers={
'Authorization': 'token %s' % github_token,
'Content-type': 'application/json',
},
)
if response.status_code != 200:
print('Failed to get repo releases on github')
print(response.json())
sys.exit(1)
for release in response.json():
print(release['tag_name'])
# Create gitlab release
resp = requests.post(
('https://%s/api/v4/projects' +
'/%s%%2F%s/repository/tags/%s/release') % (
gitlab_host, github_owner, REPO_NAME,
release['tag_name']),
headers={
'Private-Token': gitlab_token,
'Content-type': 'application/json',
},
data=json.dumps({
'tag_name': release['tag_name'],
'description': release['body'],
}),
)
if resp.status_code not in (201, 409):
print('Failed to create releases on gitlab')
print(resp.json())
sys.exit(1)
if 'Link' not in response.headers or \
'rel="next"' not in response.headers['Link']:
break
next_url = response.headers['Link'].split(';')[0][1:-1]
if cmd == 'get-version':
new_version_orig = args[1]
new_version = get_ver(new_version_orig)
print(new_version)
if cmd == 'set-version':
new_version_orig = args[1]
new_version = get_ver(new_version_orig)
is_snapshot = 'snapshot' in new_version
pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH
# Update changes
if not is_snapshot:
with open(CHANGES_PATH, 'r') as changes_file:
changes_data = changes_file.read()
with open(CHANGES_PATH, 'w') as changes_file:
ver_date_str = 'Version ' + new_version.replace(
'v', '') + cur_date.strftime(' %Y-%m-%d')
changes_file.write(changes_data.replace(
'<%= version %>',
'%s\n%s' % (ver_date_str, '-' * len(ver_date_str)),
))
# Check for duplicate version
response = requests.get(
'https://api.github.com/repos/%s/%s/releases' % (
github_owner, REPO_NAME),
headers={
'Authorization': 'token %s' % github_token,
'Content-type': 'application/json',
},
)
if response.status_code != 200:
print('Failed to get repo releases on github')
print(response.json())
sys.exit(1)
for release in response.json():
if release['tag_name'] == new_version:
print('Version already exists in github')
sys.exit(1)
# Generate changelog
version = None
release_body = ''
if not is_snapshot:
with open(CHANGES_PATH, 'r') as changelog_file:
for line in changelog_file.readlines()[2:]:
line = line.strip()
if not line or line[0] == '-':
continue
if line[:7] == 'Version':
if version:
break
version = line.split(' ')[1]
elif version:
release_body += '* %s\n' % line
if not is_snapshot and version != new_version:
print('New version does not exist in changes')
sys.exit(1)
if is_snapshot:
release_body = '* Snapshot release'
elif not release_body:
print('Failed to generate github release body')
sys.exit(1)
release_body = release_body.rstrip('\n')
# Update constants
with open(CONSTANTS_PATH, 'r') as constants_file:
constants_data = constants_file.read()
with open(CONSTANTS_PATH, 'w') as constants_file:
constants_file.write(re.sub(
'(= ".*?")',
'= "%s"' % new_version,
constants_data,
count=1,
))
# Git commit
subprocess.check_call(['git', 'reset', 'HEAD', '.'])
subprocess.check_call(['git', 'add', CHANGES_PATH])
subprocess.check_call(['git', 'add', CONSTANTS_PATH])
subprocess.check_call(['git', 'commit', '-S', '-m', 'Create new release'])
subprocess.check_call(['git', 'push'])
# Create branch
if not is_snapshot:
subprocess.check_call(['git', 'branch', new_version])
subprocess.check_call(['git', 'push', '-u', 'origin', new_version])
time.sleep(6)
# Create tag
subprocess.check_call(['git', 'tag', new_version])
subprocess.check_call(['git', 'push', '--tags'])
time.sleep(1)
# Create release
response = requests.post(
'https://api.github.com/repos/%s/%s/releases' % (
github_owner, REPO_NAME),
headers={
'Authorization': 'token %s' % github_token,
'Content-type': 'application/json',
},
data=json.dumps({
'tag_name': new_version,
'name': '%s v%s' % (REPO_NAME, new_version),
'body': release_body,
'prerelease': is_snapshot,
'target_commitish': 'master' if is_snapshot else new_version,
}),
)
if response.status_code != 201:
print('Failed to create release on github')
print(response.json())
sys.exit(1)
subprocess.check_call(['git', 'pull'])
subprocess.check_call(['git', 'push', '--tags'])
time.sleep(6)
# Create gitlab release
response = requests.post(
('https://%s/api/v4/projects' +
'/%s%%2F%s/releases') % (
gitlab_host, github_owner, REPO_NAME),
headers={
'Private-Token': gitlab_token,
'Content-type': 'application/json',
},
data=json.dumps({
'tag_name': new_version,
'name': '%s v%s' % (REPO_NAME, new_version),
'description': release_body,
}),
)
if response.status_code != 201:
print('Failed to create release on gitlab')
print(response.json())
sys.exit(1)
if cmd == 'build' or cmd == 'build-upload':
is_snapshot = 'snapshot' in cur_version
pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH
# Get sha256 sum
archive_name = '%s.tar.gz' % cur_version
archive_path = os.path.join(os.path.sep, 'tmp', archive_name)
if os.path.isfile(archive_path):
os.remove(archive_path)
wget('https://github.com/%s/%s/archive/refs/tags/%s' % (
github_owner, REPO_NAME, archive_name),
output=archive_name,
cwd=os.path.join(os.path.sep, 'tmp'),
)
archive_sha256_sum = subprocess.check_output(
['sha256sum', archive_path]).split()[0]
os.remove(archive_path)
# Update sha256 sum and pkgver in PKGBUILD
for target in BUILD_TARGETS:
pkgbuild_path = os.path.join(pacur_path, target, 'PKGBUILD')
with open(pkgbuild_path, 'r') as pkgbuild_file:
pkgbuild_data = re.sub(
'pkgver="(.*)"',
'pkgver="%s"' % cur_version,
pkgbuild_file.read(),
count=1,
)
pkgbuild_data = re.sub(
'"[a-f0-9]{64}"',
'"%s"' % archive_sha256_sum.decode('utf-8'),
pkgbuild_data,
count=1,
)
with open(pkgbuild_path, 'w') as pkgbuild_file:
pkgbuild_file.write(pkgbuild_data)
# Run pacur project build
for build_target in BUILD_TARGETS:
subprocess.check_call(
['sudo', 'pacur', 'project', 'build', build_target],
cwd=pacur_path,
)
if cmd == 'upload' or cmd == 'build-upload':
is_snapshot = 'snapshot' in cur_version
pacur_path = TEST_PACUR_PATH if is_snapshot else STABLE_PACUR_PATH
# Get release id
release_id = None
response = requests.get(
'https://api.github.com/repos/%s/%s/releases' % (
github_owner, REPO_NAME),
headers={
'Authorization': 'token %s' % github_token,
'Content-type': 'application/json',
},
)
for release in response.json():
if release['tag_name'] == cur_version:
release_id = release['id']
if not release_id:
print('Version does not exists in github')
sys.exit(1)
# Run pacur project build
subprocess.check_call(
['sudo', 'pacur', 'project', 'repo'],
cwd=pacur_path,
)
# Add to github
for name, path in iter_packages():
post_git_asset(release_id, name, path)
# Sync mirror
subprocess.check_call([
'sh',
'upload-unstable.sh',
], cwd=pacur_path)
if cmd == 'upload-github':
is_snapshot = 'snapshot' in cur_version
# Get release id
release_id = None
response = requests.get(
'https://api.github.com/repos/%s/%s/releases' % (
github_owner, REPO_NAME),
headers={
'Authorization': 'token %s' % github_token,
'Content-type': 'application/json',
},
)
for release in response.json():
if release['tag_name'] == cur_version:
release_id = release['id']
if not release_id:
print('Version does not exists in github')
sys.exit(1)
# Add to github
for name, path in iter_packages():
post_git_asset(release_id, name, path)
================================================
FILE: tools/generate_demo_data.py
================================================
#!/usr/bin/env python3
import random
import string
import hashlib
ORGANIZATION_ID = "5a3245a50accad1a8a53bc82"
DATACENTER_ID = "689733b7a7a35eae0dbaea1b"
ZONE_ID = "689733b7a7a35eae0dbaea1e"
VPC_ID = "689733b7a7a35eae0dbaea23"
NODE_IDS = [
"689733b2a7a35eae0dbaea0a",
"689733b2a7a35eae0dbaea0b",
"689733b2a7a35eae0dbaea0c",
"689733b2a7a35eae0dbaea0d",
"689733b2a7a35eae0dbaea0e",
"689733b2a7a35eae0dbaea0f",
]
NODE_NAMES = [
"pritunl-east0",
"pritunl-east1",
"pritunl-east2",
"pritunl-east3",
"pritunl-east4",
"pritunl-east5",
]
SHAPE_IDS = {
"small": "65e6e303ceeebbb3dabaec96",
"medium": "65e6e2ecceeebbb3dabaec79",
"large": "66f63282aac06d53e8c9c435",
}
IMAGE_IDS = [
"650a2c36aed15f1f1f5e96e1",
"650a2c36aed15f1f1f5e96e2",
]
POD_ID = "688bf358d978631566998ffc"
UNIT_IDS = {
"web": "688c716d9da165ffad4b3682",
"database": "68b67d1aee12c08a1f39f88b",
}
SPEC_IDS = {
"web": "688c7cde9da165ffad4b52e4",
"database": "688c7cde9da165ffad4b34f2",
}
def generate_ip(subnet_base="10.196"):
third_octet = random.randint(1, 8)
fourth_octet = random.randint(2, 254)
return f"{subnet_base}.{third_octet}.{fourth_octet}"
def generate_pub_ip(subnet_base="1.253.67"):
fourth_octet = random.randint(2, 254)
return f"{subnet_base}.{fourth_octet}"
def generate_priv_ip6(id):
hash_obj = hashlib.sha256(str(id).encode())
hash_hex = hash_obj.hexdigest()
return f"fd97:30bf:d456:a3bc:{hash_hex[0:4]}:{hash_hex[4:8]}:{hash_hex[8:12]}:{hash_hex[12:16]}"
def generate_pub_ip6(id):
hash_obj = hashlib.sha256(str(id).encode() + str(id).encode())
hash_hex = hash_obj.hexdigest()
return f"2001:db8:85a3:4d2f:{hash_hex[0:4]}:{hash_hex[4:8]}:{hash_hex[8:12]}:{hash_hex[12:16]}"
def generate_network_namespace():
characters = string.ascii_lowercase + string.digits
return ''.join(random.choice(characters) for _ in range(14))
def get_instance_spec(instance_type):
specs = {
"web": {"name": "web-app", "shape": "small", "memory": 2048, "processors": 2, "disk": 20},
"database": {"name": "database", "shape": "large", "memory": 8192, "processors": 4, "disk": 100},
"search": {"name": "search", "shape": "large", "memory": 8192, "processors": 4, "disk": 200},
"vpn": {"name": "vpn", "shape": "small", "memory": 2048, "processors": 2, "disk": 20},
}
return specs.get(instance_type)
def generate_instances(count=20):
instances = []
used_priv_ips = set()
used_host_ips = set()
used_pub_ips = set()
instance_types = (
["web"] * 10 +
["database"] * 6 +
["search"] * 2 +
["vpn"] * 2
)[:count]
for i in range(count):
instance_id = f"651d8e7c4cf9e2e3e4d56a{i:02x}"
priv_ip = generate_ip()
while priv_ip in used_priv_ips:
priv_ip = generate_ip()
used_priv_ips.add(priv_ip)
pub_ip = generate_pub_ip()
while pub_ip in used_pub_ips:
pub_ip = generate_pub_ip()
used_pub_ips.add(pub_ip)
host_ip = generate_pub_ip("198.18.84")
while host_ip in used_host_ips:
host_ip = generate_pub_ip("198.18.84")
used_host_ips.add(host_ip)
instance_type = instance_types[i]
spec = get_instance_spec(instance_type)
node_id = NODE_IDS[i % len(NODE_IDS)]
node_name = NODE_NAMES[i % len(NODE_NAMES)]
image_id = IMAGE_IDS[i % len(IMAGE_IDS)]
load1 = round(random.uniform(10, 60), 2)
load5 = round(load1 + random.uniform(1, 10), 2)
load15 = round(load5 + random.uniform(1, 10), 2)
instance = {
"id": instance_id,
"type": instance_type,
"organization": ORGANIZATION_ID,
"datacenter": DATACENTER_ID,
"zone": ZONE_ID,
"vpc": VPC_ID,
"image": image_id,
"image_backing": False,
"status": "Running",
"state": "running",
"action": "start",
"public_ips": [pub_ip],
"public_ips6": [generate_pub_ip6(instance_id)],
"private_ips": [priv_ip],
"private_ips6": [generate_priv_ip6(instance_id)],
"host_ips": [host_ip],
"node": node_id,
"node_name": node_name,
"shape": SHAPE_IDS[spec["shape"]],
"name": spec["name"],
"comment": "",
"init_disk_size": spec["disk"],
"memory": spec["memory"],
"processors": spec["processors"],
"network_namespace": generate_network_namespace(),
"mem": round(random.uniform(30, 80), 2),
"load1": load1,
"load5": load5,
"load15": load15,
}
instances.append(instance)
return instances
def generate_disks(instances):
disks = []
for i, instance in enumerate(instances):
disk_id = f"651d8e7c4cf9e2e3e4d34f{i:02x}"
disk = {
"id": disk_id,
"name": instance['name'],
"comment": "",
"state": "attached",
"type": "qcow2",
"datacenter": instance["datacenter"],
"zone": instance["zone"],
"node": instance["node"],
"organization": instance["organization"],
"instance": instance["id"],
"image": instance["image"],
"index": "0",
"size": instance["init_disk_size"],
}
disks.append(disk)
return disks
def generate_deployments(instances):
deployments = []
for i, instance in enumerate(instances):
if instance["type"] != "web" and instance["type"] != "database":
continue
deployment_id = f"651d8e7c4cf91e3b53d62d{i:02x}"
deployment = {
"id": deployment_id,
"name": instance['name'],
"type": instance['type'],
"datacenter": instance["datacenter"],
"zone": instance["zone"],
"node": instance["node"],
"node_name": instance["node_name"],
"organization": instance["organization"],
"instance": instance["id"],
"public_ips": instance["public_ips"],
"public_ips6": instance["public_ips6"],
"private_ips": instance["private_ips"],
"private_ips6": instance["private_ips6"],
"host_ips": instance["host_ips"],
"memory": instance["memory"],
"processors": instance["processors"],
"mem": instance["mem"],
"load1": instance["load1"],
"load5": instance["load5"],
"load15": instance["load15"],
}
deployments.append(deployment)
return deployments
def format_go_instance(instance):
go_code = f""" {{
Id: utils.ObjectIdHex("{instance['id']}"),
Organization: utils.ObjectIdHex("{instance['organization']}"),
Datacenter: utils.ObjectIdHex("{instance['datacenter']}"),
Zone: utils.ObjectIdHex("{instance['zone']}"),
Vpc: utils.ObjectIdHex("{instance['vpc']}"),
Image: utils.ObjectIdHex("{instance['image']}"),
ImageBacking: {str(instance['image_backing']).lower()},
Status: "{instance['status']}",
State: "{instance['state']}",
Action: "{instance['action']}",
Uptime: "5 days 11 hours 34 mins",
PublicIps: []string{{"{instance['public_ips'][0]}"}},
PublicIps6: []string{{"{instance['public_ips6'][0]}"}},
PrivateIps: []string{{"{instance['private_ips'][0]}"}},
PrivateIps6: []string{{"{instance['private_ips6'][0]}"}},
HostIps: []string{{"{instance['host_ips'][0]}"}},
Node: utils.ObjectIdHex("{instance['node']}"),
Shape: utils.ObjectIdHex("{instance['shape']}"),
Name: "{instance['name']}",
Comment: "",
InitDiskSize: {instance['init_disk_size']},
Memory: {instance['memory']},
Processors: {instance['processors']},
NetworkNamespace: "{instance['network_namespace']}",
Created: time.Now(),
Timestamp: time.Now(),
Guest: &instance.GuestData{{
Status: "running",
Timestamp: time.Now(),
Heartbeat: time.Now(),
Memory: {instance['mem']},
Load1: {instance['load1']},
Load5: {instance['load5']},
Load15: {instance['load15']},
}},
}},"""
return go_code
def format_go_disk(disk):
go_code = f""" {{
Disk: disk.Disk{{
Id: utils.ObjectIdHex("{disk['id']}"),
Name: "{disk['name']}",
Comment: "",
State: "{disk['state']}",
Type: "{disk['type']}",
Datacenter: utils.ObjectIdHex("{disk['datacenter']}"),
Zone: utils.ObjectIdHex("{disk['zone']}"),
Node: utils.ObjectIdHex("{disk['node']}"),
Organization: utils.ObjectIdHex("{disk['organization']}"),
Instance: utils.ObjectIdHex("{disk['instance']}"),
Image: utils.ObjectIdHex("{disk['image']}"),
Index: "{disk['index']}",
Size: {disk['size']},
Created: time.Now(),
}},
}},"""
return go_code
def format_go_deployment(deployment):
go_code = f""" {{
Id: utils.ObjectIdHex("{deployment['id']}"),
Pod: utils.ObjectIdHex("{POD_ID}"),
Unit: utils.ObjectIdHex("{UNIT_IDS[deployment['type']]}"),
Spec: utils.ObjectIdHex("{SPEC_IDS[deployment['type']]}"),
SpecOffset: 0,
SpecIndex: 2,
SpecTimestamp: time.Now(),
Timestamp: time.Now(),
Tags: []string{{}},
Kind: "instance",
State: "deployed",
Action: "",
Status: "healthy",
Node: utils.ObjectIdHex("{deployment['node']}"),
Instance: utils.ObjectIdHex("{deployment['instance']}"),
InstanceData: &deployment.InstanceData{{
HostIps: []string{{"{deployment['host_ips'][0]}"}},
PublicIps: []string{{"{deployment['public_ips'][0]}"}},
PublicIps6: []string{{"{deployment['public_ips6'][0]}"}},
PrivateIps: []string{{"{deployment['private_ips'][0]}"}},
PrivateIps6: []string{{"{deployment['private_ips6'][0]}"}},
}},
ZoneName: "us-west-1a",
NodeName: "{deployment['node_name']}",
InstanceName: "{deployment['name']}",
InstanceRoles: []string{{"instance"}},
InstanceMemory: {deployment['memory']},
InstanceProcessors: {deployment['processors']},
InstanceStatus: "Running",
InstanceUptime: "5 days",
InstanceState: "running",
InstanceAction: "start",
InstanceGuestStatus: "running",
InstanceTimestamp: time.Now(),
InstanceHeartbeat: time.Now(),
InstanceMemoryUsage: {deployment['mem']},
InstanceHugePages: 0,
InstanceLoad1: {deployment['load1']},
InstanceLoad5: {deployment['load5']},
InstanceLoad15: {deployment['load15']},
}},"""
return go_code
def main():
instances = generate_instances(20)
disks = generate_disks(instances)
deployments = generate_deployments(instances)
print("// Instances")
print("var Instances = []*instance.Instance{")
for i, instance in enumerate(instances):
print(format_go_instance(instance))
print("}")
print("")
print("// Disks")
print("var Disks = []*aggregate.DiskAggregate{")
for i, disk in enumerate(disks):
print(format_go_disk(disk))
print("}")
print("")
print("// Deployments")
print("var Deployments = []*aggregate.Deployment{")
for i, deployment in enumerate(deployments):
print(format_go_deployment(deployment))
print("}")
if __name__ == "__main__":
main()
================================================
FILE: tools/generate_files.py
================================================
import os
import shutil
import subprocess
import json
from datetime import datetime, timezone
def md5_hash(filepath):
result = subprocess.run(["md5sum", filepath], stdout=subprocess.PIPE)
return result.stdout.split()[0].decode("utf-8")
def last_modified_time(filepath):
timestamp = os.path.getmtime(filepath)
return datetime.fromtimestamp(timestamp,
tz=timezone.utc).strftime("%Y-%m-%dT%H:%M:%S") + "Z"
def create_files_json(directory, output_file):
existing_entries = {}
if os.path.exists(output_file):
backup_file = "{}.{}.bak".format(
output_file,
datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ"),
)
shutil.copy2(output_file, backup_file)
print("Backed up {} to {}".format(output_file, backup_file))
with open(output_file, "r") as f:
existing_data = json.load(f)
for entry in existing_data.get("files", []):
existing_entries[entry["name"]] = entry
files_data = {
"version": 1,
"files": [],
}
seen_names = set()
for root, _, filenames in os.walk(directory):
for filename in sorted(filenames):
if not filename.endswith(".qcow2"):
continue
if filename in seen_names:
continue
seen_names.add(filename)
if filename in existing_entries:
files_data["files"].append(existing_entries[filename])
continue
filepath = os.path.join(root, filename)
print("Hashing new file {}".format(filename))
file_data = {
"name": filename,
"signed": True,
"hash": md5_hash(filepath),
"last_modified": last_modified_time(filepath),
}
files_data["files"].append(file_data)
for name, entry in existing_entries.items():
if name not in seen_names:
files_data["files"].append(entry)
files_data["files"].sort(key=lambda e: e["name"])
with open(output_file, "w") as f:
json.dump(files_data, f, indent=4)
create_files_json(os.getcwd(), "files.json")
================================================
FILE: tools/package/PKGBUILD
================================================
targets=(
"oraclelinux-10"
)
pkgname="pritunl-cloud"
pkgver="2.0.3665.99"
pkgrel="2"
pkgdesc="Pritunl Cloud"
pkgdesclong=(
"Pritunl Cloud"
)
maintainer="Pritunl "
arch="amd64"
license=("custom")
section="utils"
priority="optional"
url="https://github.com/pritunl/${pkgname}"
depends:yum=(
"iptables"
"net-tools"
"ipset"
"ipvsadm"
"xorriso"
)
depends:apt=(
"iptables"
"net-tools"
"ipset"
"ipvsadm"
"xorriso"
)
optdepends:yum=(
"qemu-kvm"
"qemu-img"
"swtpm"
"swtpm-tools"
)
makedepends:yum=(
"git"
)
makedepends:apt=(
"git"
)
provides=("${pkgname}")
conflicts=(
"${pkgname}"
)
sources=(
"pritunl-cloud.tar"
)
hashsums=(
"690c2aab9a0ea6a940b4390b6e95ec55c10951e3634e51115a36051402d686bc"
)
backup=(
"etc/${pkgname}.json"
"var/log/${pkgname}.log"
"var/log/${pkgname}.log.1"
)
build() {
mkdir -p /go/src/github.com/pritunl/${pkgname}/
mv "${srcdir}"/* /go/src/github.com/pritunl/${pkgname}/
cd /go/src/github.com/pritunl/${pkgname}
go get
go install
cd /go/src/github.com/pritunl/${pkgname}/agent
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -ldflags '-extldflags "-static"' -v -o agent-static
mv ./agent-static /go/bin/${pkgname}-agent
CGO_ENABLED=0 GOOS=freebsd GOARCH=amd64 go build -v -o agent-bsd
mv ./agent-bsd /go/bin/${pkgname}-agent-bsd
cd /go/src/github.com/pritunl/${pkgname}/redirect
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -v -o redirect
mv ./redirect /go/bin/${pkgname}-redirect
}
package() {
mkdir -p ${pkgdir}/usr/bin
cp /go/bin/${pkgname} ${pkgdir}/usr/bin/${pkgname}
chmod 755 ${pkgdir}/usr/bin/${pkgname}
cp /go/bin/${pkgname}-redirect ${pkgdir}/usr/bin/${pkgname}-redirect
chmod 755 ${pkgdir}/usr/bin/${pkgname}-redirect
cp /go/bin/${pkgname}-agent ${pkgdir}/usr/bin/${pkgname}-agent
chmod 755 ${pkgdir}/usr/bin/${pkgname}-agent
cp /go/bin/${pkgname}-agent-bsd ${pkgdir}/usr/bin/${pkgname}-agent-bsd
chmod 755 ${pkgdir}/usr/bin/${pkgname}-agent-bsd
mkdir -p ${pkgdir}/usr/share/${pkgname}/www
cp -r /go/src/github.com/pritunl/${pkgname}/www/dist/. ${pkgdir}/usr/share/${pkgname}/www/
mkdir -p ${pkgdir}/etc
echo "{}" > ${pkgdir}/etc/${pkgname}.json
chmod 600 ${pkgdir}/etc/${pkgname}.json
mkdir -p ${pkgdir}/etc/systemd/system
cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud.service ${pkgdir}/etc/systemd/system
cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud-redirect.socket ${pkgdir}/etc/systemd/system
cp /go/src/github.com/pritunl/${pkgname}/tools/pritunl-cloud-redirect.service ${pkgdir}/etc/systemd/system
mkdir -p ${pkgdir}/var/log
touch ${pkgdir}/var/log/${pkgname}.log
touch ${pkgdir}/var/log/${pkgname}.log.1
}
postinst() {
useradd -r -s /sbin/nologin -c 'Pritunl Cloud web server' pritunl-cloud-web &> /dev/null || true
systemctl daemon-reload &> /dev/null || true
systemctl is-active --quiet pritunl-cloud && systemctl restart pritunl-cloud || true
}
postrm() {
systemctl daemon-reload &> /dev/null || true
}
================================================
FILE: tools/package/README.md
================================================
# pritunl-cloud: package
Build test package
## install pacur
```bash
sudo dnf -y install git-core podman
sudo rm -rf /usr/local/go
wget https://go.dev/dl/go1.25.4.linux-amd64.tar.gz
echo "9fa5ffeda4170de60f67f3aa0f824e426421ba724c21e133c1e35d6159ca1bec go1.25.4.linux-amd64.tar.gz" | sha256sum -c - && sudo tar -C /usr/local -xf go1.25.4.linux-amd64.tar.gz
rm -f go1.25.4.linux-amd64.tar.gz
tee -a ~/.bashrc << EOF
export GO111MODULE=on
export GOPATH=\$HOME/go
export GOROOT=/usr/local/go
export PATH=/usr/local/go/bin:\$PATH:\$HOME/go/bin
EOF
chown cloud:cloud ~/.bashrc
source ~/.bashrc
go install github.com/pacur/pacur@latest
cd "$(ls -d ~/go/pkg/mod/github.com/pacur/pacur@*/docker/ | sort -V | tail -n 1)"
sudo find . -maxdepth 1 -type d -name "*" ! -name "." ! -name ".." ! -name "oraclelinux-10" -exec rm -rf {} +
sh clean.sh
sh build.sh
cd
```
## build package
```bash
git clone https://github.com/pritunl/pritunl-cloud.git
cd pritunl-cloud/tools/package
sudo podman run --rm -t -v `pwd`:/pacur:Z localhost/pacur/oraclelinux-10
```
================================================
FILE: tools/pritunl-cloud-redirect.service
================================================
[Unit]
Description=Pritunl Cloud Redirect Server Daemon
Requires=pritunl-cloud-redirect.socket
After=pritunl-cloud-redirect.socket
[Service]
ExecStart=/usr/bin/pritunl-cloud-redirect
EnvironmentFile=/var/lib/pritunl-cloud/redirect.conf
User=pritunl-cloud-web
Group=pritunl-cloud-web
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
PrivateNetwork=true
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
LockPersonality=true
SystemCallFilter=@system-service
SystemCallArchitectures=native
RestrictSUIDSGID=true
DevicePolicy=closed
CapabilityBoundingSet=
AmbientCapabilities=
NoNewPrivileges=true
IPAddressDeny=any
SocketBindDeny=any
ReadOnlyPaths=/
InaccessiblePaths=/home /root /boot /opt /mnt /media
[Install]
WantedBy=multi-user.target
================================================
FILE: tools/pritunl-cloud-redirect.socket
================================================
[Unit]
Description=Pritunl Cloud Redirect Server Socket
[Socket]
ListenStream=80
Accept=no
[Install]
WantedBy=sockets.target
================================================
FILE: tools/pritunl-cloud.service
================================================
[Unit]
Description=Pritunl Cloud Daemon
[Service]
LimitNOFILE=50000
ExecStart=/usr/bin/pritunl-cloud start
[Install]
WantedBy=multi-user.target
================================================
FILE: tools/tsc_run.sh
================================================
#!/bin/bash
set -e
npx tsc --watch
================================================
FILE: tools/virt-install/README.md
================================================
# pritunl-cloud: virt-install scripts
Scripts used to build base images for pritunl-cloud
```bash
sudo tee /etc/security/limits.conf << EOF
* soft memlock 2048000000
* hard memlock 2048000000
root soft memlock 2048000000
root hard memlock 2048000000
* hard nofile 500000
* soft nofile 500000
root hard nofile 500000
root soft nofile 500000
EOF
sudo tee /etc/systemd/system/disable-thp.service << EOF
[Unit]
Description=Disable Transparent Huge Pages
[Service]
Type=simple
ExecStart=/bin/sh -c "echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled && echo 'never' > /sys/kernel/mm/transparent_hugepage/defrag"
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl start disable-thp
sudo systemctl enable disable-thp
sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config
sudo setenforce 0
sudo dnf -y install qemu-kvm qemu-img libguestfs-tools xorriso edk2-ovmf libvirt virt-install
sudo systemctl enable --now libvirtd
cd ./setup
sudo firewall-cmd --zone=libvirt --add-port=8000/tcp --permanent
python3 -m http.server
# alpine linux
setup-alpine
curl -o /root/setup.sh http://192.168.122.1:8000/alpine.sh
echo "c502a8b650d2b60f61414ea2f286577732ab7fc96bac487ebf024cd2120244ca /root/setup.sh" | sha256sum -c && sudo sh /root/setup.sh
# arch linux
mkdir /mnt/config
mount /dev/sr1 /mnt/config
cp /mnt/config/archinstall.json /root
umount /mnt/config
rmdir /mnt/config
archinstall --silent --config /root/archinstall.json
reboot
curl -o /root/setup.sh http://192.168.122.1:8000/arch.sh
echo "412aacb35f882d09ad7390124f2e3f52a7ae8deb6aaf2825a8775912dfb058fd /root/setup.sh" | sha256sum -c && bash /root/setup.sh
# debian
sudo curl -o /root/setup.sh http://192.168.122.1:8000/debian.sh
echo "5fe9beb585bc434a8ebc8a32fbca347d8180ebc2cf6aef014b06b8c82a1f802a /root/setup.sh" | sudo sha256sum -c && sudo bash /root/setup.sh
# fedora
curl -o /root/setup.sh http://192.168.122.1:8000/fedora.sh
echo "18032b049b0410f6c78ad5cfcd5f9b65fa3955e58268dc86ca41d7dbfb66a465 /root/setup.sh" | sha256sum -c && bash /root/setup.sh
# freebsd
fetch -o /root/setup.sh http://192.168.122.1:8000/freebsd.sh
[ "$(sha256sum /root/setup.sh)" = "6aab203e3ba7c8aa31ad9dc7da38f701f3871a1ae339904e1f1e6f774ec58238 /root/setup.sh" ] && sh /root/setup.sh
# rhel7
curl -o /root/setup.sh http://192.168.122.1:8000/rhel7.sh
echo "da5f9518e45a71f1348b7fffd14e496a64cf2bb4a73fc763ec8e97d8f4c2e6d6 /root/setup.sh" | sha256sum -c && bash /root/setup.sh
# rhel8
curl -o /root/setup.sh http://192.168.122.1:8000/rhel8.sh
echo "dd277240c6d5b573f34e98241c67caec8c0b3c855d13fbb8ccfdfda7f7e726fa /root/setup.sh" | sha256sum -c && bash /root/setup.sh
# rhel9
curl -o /root/setup.sh http://192.168.122.1:8000/rhel9.sh
echo "23e0b0191270db7e09ade9afce206ac6a455aa7e91bf9eda6b6c677dfb78d994 /root/setup.sh" | sha256sum -c && bash /root/setup.sh
# rhel10
curl -o /root/setup.sh http://192.168.122.1:8000/rhel10.sh
echo "49cd8fd80e0a3badfbfa1b62de270e62e8f33e831e7f4780fa2f38bd89b9ffe5 /root/setup.sh" | sha256sum -c && bash /root/setup.sh
find /var/lib/virt/images/ -name "*_$(date +%y%m%d).qcow2" -type f -exec sudo GPG_TTY=$(tty) gpg --default-key 055C08A4 --armor --output {}.sig --detach-sig {} \;
sudo mkdir -p /mnt/images
sudo chown cloud:cloud /mnt/images
mkdir -p /mnt/images/stable
mkdir -p /mnt/images/unstable
rsync --human-readable --archive --xattrs --progress 127.0.0.1:/var/lib/virt/images/ /mnt/images/unstable/
sudo wget -P /tmp https://raw.githubusercontent.com/pritunl/toolbox/73aacb9e22b09a34f87d389b3dc301d6c450b0e8/s3c/s3c.py
echo "7d14fa361e47ff328bbadac302a06a995f6ab65abbe4efce7d8cde6657ba8dde /tmp/s3c.py" | sha256sum -c - && sudo cp /tmp/s3c.py /usr/local/bin/s3c && sudo chmod +x /usr/local/bin/s3c
sudo rm /tmp/s3c.py
cd /mnt/images/unstable
python3 ~/git/pritunl-cloud/tools/generate_files.py
python3 ~/git/pritunl-cloud/tools/autoindex.py
s3c cp almalinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux8_$(date +%y%m%d).qcow2
s3c cp almalinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux8_$(date +%y%m%d).qcow2.sig
s3c cp almalinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux9_$(date +%y%m%d).qcow2
s3c cp almalinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux9_$(date +%y%m%d).qcow2.sig
s3c cp almalinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/almalinux10_$(date +%y%m%d).qcow2
s3c cp almalinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/almalinux10_$(date +%y%m%d).qcow2.sig
s3c cp alpinelinux_$(date +%y%m%d).qcow2 pritunl-images:/unstable/alpinelinux_$(date +%y%m%d).qcow2
s3c cp alpinelinux_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/alpinelinux_$(date +%y%m%d).qcow2.sig
s3c cp archlinux_$(date +%y%m%d).qcow2 pritunl-images:/unstable/archlinux_$(date +%y%m%d).qcow2
s3c cp archlinux_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/archlinux_$(date +%y%m%d).qcow2.sig
s3c cp fedora43_$(date +%y%m%d).qcow2 pritunl-images:/unstable/fedora43_$(date +%y%m%d).qcow2
s3c cp fedora43_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/fedora43_$(date +%y%m%d).qcow2.sig
s3c cp fedora44_$(date +%y%m%d).qcow2 pritunl-images:/unstable/fedora44_$(date +%y%m%d).qcow2
s3c cp fedora44_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/fedora44_$(date +%y%m%d).qcow2.sig
s3c cp freebsd_$(date +%y%m%d).qcow2 pritunl-images:/unstable/freebsd_$(date +%y%m%d).qcow2
s3c cp freebsd_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/freebsd_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux7_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux7_$(date +%y%m%d).qcow2
s3c cp oraclelinux7_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux7_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux8_$(date +%y%m%d).qcow2
s3c cp oraclelinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux8_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux9_$(date +%y%m%d).qcow2
s3c cp oraclelinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux9_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/oraclelinux10_$(date +%y%m%d).qcow2
s3c cp oraclelinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/oraclelinux10_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux8_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux8_$(date +%y%m%d).qcow2
s3c cp rockylinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux8_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux9_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux9_$(date +%y%m%d).qcow2
s3c cp rockylinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux9_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux10_$(date +%y%m%d).qcow2 pritunl-images:/unstable/rockylinux10_$(date +%y%m%d).qcow2
s3c cp rockylinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/rockylinux10_$(date +%y%m%d).qcow2.sig
s3c cp ubuntu2404_$(date +%y%m%d).qcow2 pritunl-images:/unstable/ubuntu2404_$(date +%y%m%d).qcow2
s3c cp ubuntu2404_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/ubuntu2404_$(date +%y%m%d).qcow2.sig
s3c cp ubuntu2604_$(date +%y%m%d).qcow2 pritunl-images:/unstable/ubuntu2604_$(date +%y%m%d).qcow2
s3c cp ubuntu2604_$(date +%y%m%d).qcow2.sig pritunl-images:/unstable/ubuntu2604_$(date +%y%m%d).qcow2.sig
s3c cp files.json pritunl-images:/unstable/files.json
s3c cp index.html pritunl-images:/unstable/index.html
rsync --human-readable --archive --progress --delete /mnt/images/unstable/ /mnt/images/stable/
cd /mnt/images/stable
python3 ~/git/pritunl-cloud/tools/generate_files.py
python3 ~/git/pritunl-cloud/tools/autoindex.py
s3c cp almalinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux8_$(date +%y%m%d).qcow2
s3c cp almalinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux8_$(date +%y%m%d).qcow2.sig
s3c cp almalinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux9_$(date +%y%m%d).qcow2
s3c cp almalinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux9_$(date +%y%m%d).qcow2.sig
s3c cp almalinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/almalinux10_$(date +%y%m%d).qcow2
s3c cp almalinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/almalinux10_$(date +%y%m%d).qcow2.sig
s3c cp alpinelinux_$(date +%y%m%d).qcow2 pritunl-images:/stable/alpinelinux_$(date +%y%m%d).qcow2
s3c cp alpinelinux_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/alpinelinux_$(date +%y%m%d).qcow2.sig
s3c cp archlinux_$(date +%y%m%d).qcow2 pritunl-images:/stable/archlinux_$(date +%y%m%d).qcow2
s3c cp archlinux_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/archlinux_$(date +%y%m%d).qcow2.sig
s3c cp fedora43_$(date +%y%m%d).qcow2 pritunl-images:/stable/fedora43_$(date +%y%m%d).qcow2
s3c cp fedora43_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/fedora43_$(date +%y%m%d).qcow2.sig
s3c cp fedora44_$(date +%y%m%d).qcow2 pritunl-images:/stable/fedora44_$(date +%y%m%d).qcow2
s3c cp fedora44_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/fedora44_$(date +%y%m%d).qcow2.sig
s3c cp freebsd_$(date +%y%m%d).qcow2 pritunl-images:/stable/freebsd_$(date +%y%m%d).qcow2
s3c cp freebsd_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/freebsd_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux7_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux7_$(date +%y%m%d).qcow2
s3c cp oraclelinux7_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux7_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux8_$(date +%y%m%d).qcow2
s3c cp oraclelinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux8_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux9_$(date +%y%m%d).qcow2
s3c cp oraclelinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux9_$(date +%y%m%d).qcow2.sig
s3c cp oraclelinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/oraclelinux10_$(date +%y%m%d).qcow2
s3c cp oraclelinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/oraclelinux10_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux8_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux8_$(date +%y%m%d).qcow2
s3c cp rockylinux8_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux8_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux9_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux9_$(date +%y%m%d).qcow2
s3c cp rockylinux9_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux9_$(date +%y%m%d).qcow2.sig
s3c cp rockylinux10_$(date +%y%m%d).qcow2 pritunl-images:/stable/rockylinux10_$(date +%y%m%d).qcow2
s3c cp rockylinux10_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/rockylinux10_$(date +%y%m%d).qcow2.sig
s3c cp ubuntu2404_$(date +%y%m%d).qcow2 pritunl-images:/stable/ubuntu2404_$(date +%y%m%d).qcow2
s3c cp ubuntu2404_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/ubuntu2404_$(date +%y%m%d).qcow2.sig
s3c cp ubuntu2604_$(date +%y%m%d).qcow2 pritunl-images:/stable/ubuntu2604_$(date +%y%m%d).qcow2
s3c cp ubuntu2604_$(date +%y%m%d).qcow2.sig pritunl-images:/stable/ubuntu2604_$(date +%y%m%d).qcow2.sig
s3c cp files.json pritunl-images:/stable/files.json
s3c cp index.html pritunl-images:/stable/index.html
```
================================================
FILE: tools/virt-install/download.sh
================================================
#!/bin/bash
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
INSTALL_DIR="$SCRIPT_DIR/install"
if [ ! -d "$INSTALL_DIR" ]; then
echo "Error: install directory not found at: $INSTALL_DIR"
exit 1
fi
if ! command -v wget &> /dev/null; then
echo "Error: wget is not installed"
exit 1
fi
processed=0
downloaded=0
for script in "$INSTALL_DIR"/*.sh; do
if [ ! -f "$script" ]; then
echo "No .sh files found in $INSTALL_DIR"
exit 0
fi
echo "Processing: $script"
((processed++))
iso_url=$(grep -E "^[[:space:]]*ISO_URL=" "$script" | tail -1 | cut -d'=' -f2- | sed 's/^["'\'']//' | sed 's/["'\'']$//')
if [ -z "$iso_url" ]; then
echo " No ISO_URL found in $script"
continue
fi
iso_url=$(echo "$iso_url" | sed 's/\${\([^}]*\)}/\1/g' | sed 's/\$\([A-Za-z_][A-Za-z0-9_]*\)/\1/g')
if [ -f "/var/lib/virt/iso/$(basename ${iso_url})" ]; then
echo " File already exists, skipping"
continue
fi
echo " Downloading: $iso_url"
if sudo wget -P /var/lib/virt/iso "$iso_url"; then
echo " Successfully downloaded: $iso_url"
((downloaded++))
else
echo " Failed to download from: $iso_url"
fi
echo ""
done
echo "Summary:"
echo " Processed $processed .sh files"
echo " Successfully downloaded $downloaded files"
================================================
FILE: tools/virt-install/install/almalinux10.sh
================================================
#!/bin/bash
set -ev
NAME="almalinux10"
ISO_URL="https://den.aws.repo.almalinux.org/10.1/isos/x86_64/AlmaLinux-10.1-x86_64-dvd.iso"
ISO_HASH="4597a7483fd7b49bbb7a46958fe2574b7c575531e5ee64ad4d6c1d2779494400"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ${NAME} \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/almalinux8.sh
================================================
#!/bin/bash
set -ev
NAME="almalinux8"
ISO_URL="https://den.aws.repo.almalinux.org/8.10/isos/x86_64/AlmaLinux-8.10-x86_64-dvd.iso"
ISO_HASH="463fa92155b886e31627f6713e1c2824343762245a914715ffd6f2efc300b7a1"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ${NAME} \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/almalinux9.sh
================================================
#!/bin/bash
set -ev
NAME="almalinux9"
ISO_URL="https://den.aws.repo.almalinux.org/9.7/isos/x86_64/AlmaLinux-9.7-x86_64-dvd.iso"
ISO_HASH="56f8bf5e44d293a040203b73b70f08bb7dc52f27654b047c20be8598f63ec1f8"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ${NAME} \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/alpinelinux.sh
================================================
#!/bin/bash
set -ev
NAME="alpinelinux"
ISO_URL="https://dl-cdn.alpinelinux.org/alpine/v3.22/releases/x86_64/alpine-virt-3.22.2-x86_64.iso"
ISO_HASH="b6c45d69829b1b0416ada798353805099d57b8bef9093b85a8319fe5373595d5"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo mkdir -p /var/lib/virt/init
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo mkdir /var/lib/virt/init/${NAME}
sudo tee /var/lib/virt/init/${NAME}/user-data << EOF
#alpine-config
apk:
repositories:
- base_url: https://dl-cdn.alpinelinux.org/alpine
repos:
- main
- community
packages:
- curl
- dosfstools
- grub-efi
- xfsprogs
- xfsprogs-extra
- sudo
- chrony
- openssh
- qemu-guest-agent
- cloud-init
- cloud-utils-growpart
runcmd:
- rm /etc/runlevels/*/tiny-cloud*
- lbu include /root/.ssh /home/alpine/.ssh
- ERASE_DISKS=/dev/vda USE_EFI=1 DISKLABEL=gpt ROOTFS=xfs BOOT_SIZE=100 SWAP_SIZE=0 setup-disk -m sys /dev/vda
- reboot
EOF
sudo tee /var/lib/virt/init/${NAME}/meta-data << EOF
hostname: cloud
EOF
sudo rm -f /var/lib/virt/init/${NAME}.iso
sudo xorriso -as mkisofs \
-output /var/lib/virt/init/${NAME}.iso \
-volid cidata \
-joliet \
-rock \
-input-charset utf-8 \
/var/lib/virt/init/${NAME}/user-data \
/var/lib/virt/init/${NAME}/meta-data
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant alpinelinux3.20 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=boot/vmlinuz-virt,initrd=boot/initramfs-virt \
--cloud-init meta-data=/var/lib/virt/init/${NAME}/meta-data,user-data=/var/lib/virt/init/${NAME}/user-data \
--extra-args="console=ttyS0 autoinstall"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
sudo rm -f /var/lib/virt/init/${NAME}.iso
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/archlinux.sh
================================================
#!/bin/bash
set -ev
NAME="archlinux"
ISO_URL="https://cofractal-sea.mm.fcix.net/archlinux/iso/latest/archlinux-2026.04.01-x86_64.iso"
ISO_HASH="f14bf46afbe782d28835aed99bfa2fe447903872cb9f4b21153196d6ed1d48ae"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/images
sudo mkdir -p /var/lib/virt/init
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo mkdir /var/lib/virt/init/${NAME}
sudo tee /var/lib/virt/init/${NAME}/archinstall.json << EOF
{
"additional-repositories": null,
"archinstall-language": "English",
"audio_config": null,
"bootloader": "Systemd-boot",
"debug": false,
"disk_config": {
"config_type": "manual_partitioning",
"device_modifications": [
{
"device": "/dev/vda",
"partitions": [
{
"btrfs": [],
"dev_path": null,
"flags": ["boot", "esp"],
"fs_type": "fat32",
"size": {
"sector_size": {
"unit": "B",
"value": 512
},
"unit": "MiB",
"value": 512
},
"mount_options": [],
"mountpoint": "/boot",
"obj_id": "$(uuidgen)",
"start": {
"sector_size": {
"unit": "B",
"value": 512
},
"unit": "MiB",
"value": 1
},
"status": "create",
"type": "primary"
},
{
"btrfs": [],
"dev_path": null,
"flags": [],
"fs_type": "xfs",
"size": {
"sector_size": {
"unit": "B",
"value": 512
},
"unit": "MiB",
"value": 7678
},
"mount_options": [],
"mountpoint": "/",
"obj_id": "$(uuidgen)",
"start": {
"sector_size": {
"unit": "B",
"value": 512
},
"unit": "MiB",
"value": 513
},
"status": "create",
"type": "primary"
}
],
"wipe": true
}
]
},
"hostname": "cloud",
"kernels": ["linux"],
"locale_config": {
"kb_layout": "us",
"sys_enc": "UTF-8",
"sys_lang": "en_US"
},
"network_config": {
"type": "nm"
},
"no_pkg_lookups": false,
"ntp": true,
"offline": false,
"packages": [
"base",
"base-devel",
"linux",
"linux-firmware",
"networkmanager",
"openssh",
"efibootmgr",
"vi"
],
"parallel downloads": 0,
"profile_config": {
"gfx_driver": null,
"greeter": null,
"profile": {
"custom_settings": {},
"details": [],
"main": "Server"
}
},
"mirror_config": {
"custom_mirrors": [],
"mirror_regions": {
"Worldwide": [
"https://geo.mirror.pkgbuild.com/\$repo/os/\$arch",
"https://mirror.rackspace.com/archlinux/\$repo/os/\$arch"
]
}
},
"custom_commands": [
"sed -i 's/rootfstype=xfs\$/rootfstype=xfs console=ttyS0/' /boot/loader/entries/*.conf",
"systemctl enable serial-getty@ttyS0.service",
"systemctl set-default multi-user.target",
"mkinitcpio -P"
],
"save_config": null,
"script": "guided",
"silent": true,
"swap": false,
"timezone": "UTC",
"version": "2.8.6",
"root_enc_password": "\$y\$j9T\$KsJ3WRqoGvcjGsQNis/oG0\$0zE1DqJ4NJn6pEN3VhnaUIA/nIBSeIYNR8yShbphLW1"
}
EOF
sudo rm -f /var/lib/virt/init/${NAME}/archinstall.iso
sudo xorriso -as mkisofs \
-output /var/lib/virt/init/${NAME}/archinstall.iso \
-volid CONFIGDATA \
-joliet \
-rock \
-input-charset utf-8 \
/var/lib/virt/init/${NAME}/archinstall.json
sudo tee /var/lib/virt/init/${NAME}/archinstall-auto.service << 'EOF'
[Unit]
Description=Automated Arch Installation
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/bin/archinstall --silent --config /archinstall-config.json
ExecStartPost=/usr/bin/systemctl poweroff
[Install]
WantedBy=multi-user.target
EOF
sudo tee /var/lib/virt/init/${NAME}/archinstall-auto.service << 'EOF'
[Unit]
Description=Automated Arch Installation
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/usr/bin/archinstall --config /archinstall-config.json --silent
ExecStartPost=/usr/bin/systemctl poweroff
[Install]
WantedBy=multi-user.target
EOF
sudo tee /var/lib/virt/init/${NAME}/autoinstall.sh << 'EOF'
#!/bin/bash
set -ex
mkdir /mnt/config
mount /dev/sr1 /mnt/config
cp /mnt/config/archinstall.json /root/
umount /mnt/config
rmdir /mnt/config
archinstall --silent --config /root/archinstall.json
reboot
EOF
sudo chmod +x /var/lib/virt/init/${NAME}/autoinstall.sh
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--disk path=/var/lib/virt/init/${NAME}/archinstall.iso,device=cdrom,bus=sata \
--os-variant archlinux \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=arch/boot/x86_64/vmlinuz-linux,initrd=arch/boot/x86_64/initramfs-linux.img \
--extra-args="console=ttyS0 archisobasedir=arch archisosearchuuid=$(blkid -s UUID -o value /var/lib/virt/iso/$(basename ${ISO_URL})) cow_spacesize=1G"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/fedora43.sh
================================================
#!/bin/bash
set -ev
NAME="fedora43"
ISO_URL="https://cofractal-sea.mm.fcix.net/fedora/linux/releases/43/Server/x86_64/iso/Fedora-Server-dvd-x86_64-43-1.6.iso"
ISO_HASH="aca06983bef83da9b43144c1a2ff4c8483e4745167c17f53725c16a16742e643"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^server-product-environment
%end
firstboot --enable
skipx
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant fedora40 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/fedora44.sh
================================================
#!/bin/bash
set -ev
NAME="fedora44"
ISO_URL="https://cofractal-sea.mm.fcix.net/fedora/linux/releases/44/Server/x86_64/iso/Fedora-Server-dvd-x86_64-44-1.7.iso"
ISO_HASH="85837793bfa36db6bc709b4cecd2ec116951b87d9c53c3d95eb2fac8dcf7cf1f"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^server-product-environment
%end
firstboot --enable
skipx
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant fedora40 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/freebsd.sh
================================================
#!/bin/bash
set -ev
NAME="freebsd"
ISO_URL="https://download.freebsd.org/releases/amd64/amd64/ISO-IMAGES/15.0/FreeBSD-15.0-RELEASE-amd64-dvd1.iso"
ISO_HASH="8cf8e03d8df16401fd5a507480a3270091aa30b59ecf79a9989f102338e359aa"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
# Console type [vt100]: Enter
# Welcome: Enter
# Keymap Selection: Enter
# Set Hostname: cloud
# Select Installation Type: Distribution Sets
# Distribution Select: Enter
# Partitioning: Auto (UFS)
# Partition: Enter
# Partition Scheme: Enter
# Partition Editor: Delete freebsd-swap
# Partition Editor: Enter
# New Password: cloud
# Retype New Password: cloud
# Network Configuration: Manual
# Network Configuration: Enter
# Network Configuration: Enter
# Network Configuration: Enter
# Network Configuration: No
# IPv4 DNS #1: 8.8.8.8
# IPv4 DNS #2: 8.8.4.4
# Time Zone Selector: Enter
# Time Zone Confirmation: Enter
# Time & Date: Skip
# Time & Date: Skip
# System Configuration: +ntpd +ntpd_sync_on_start
# System Hardening: Enter
# Add User Accounts: Enter
# Username: cloud
# Full name: Cloud
# Uid: 1000
# Login group: Enter
# Invite cloud into other groups: wheel
# Login class: Enter
# Shell: tcsh
# Home directory: Enter
# Home directory permissions: Enter
# Use password-based authentication: no
# Lock out the account after creation: Enter
# OK: yes
# Add another user: Enter
# Final Configuration: Exit
# Manual Configuration: Enter
# Complete: Enter
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant freebsd14.0 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--cdrom=/var/lib/virt/iso/$(basename ${ISO_URL})
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/oraclelinux10.sh
================================================
#!/bin/bash
set -ev
NAME="oraclelinux10"
ISO_URL="https://yum.oracle.com/ISOS/OracleLinux/OL10/u1/x86_64/OracleLinux-R10-U1-x86_64-dvd.iso"
ISO_HASH="82fa2b70a18fb268c5ef013e298f85bba0d0e6c7ae882c49a3f67c02ee6d68de"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ol9.5 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/oraclelinux7.sh
================================================
#!/bin/bash
set -ev
NAME="oraclelinux7"
ISO_URL="https://yum.oracle.com/ISOS/OracleLinux/OL7/u9/x86_64/OracleLinux-R7-U9-Server-x86_64-dvd.iso"
ISO_HASH="28d2928ded40baddcd11884b9a6a611429df12897784923c346057ec5cdd1012"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal
@core
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ol7.9 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/oraclelinux8.sh
================================================
#!/bin/bash
set -ev
NAME="oraclelinux8"
ISO_URL="https://yum.oracle.com/ISOS/OracleLinux/OL8/u10/x86_64/OracleLinux-R8-U10-x86_64-dvd.iso"
ISO_HASH="7676a80eeaafa16903eebb2abba147a3afe230b130cc066d56fdd6854d8da900"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ol8.10 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/oraclelinux9.sh
================================================
#!/bin/bash
set -ev
NAME="oraclelinux9"
ISO_URL="https://yum.oracle.com/ISOS/OracleLinux/OL9/u7/x86_64/OracleLinux-R9-U7-x86_64-dvd.iso"
ISO_HASH="895751f157727bca8437607e8edb32b7c10d815aba954f5b3dc24c28ac8a10aa"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant ol9.5 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/rockylinux10.sh
================================================
#!/bin/bash
set -ev
NAME="rockylinux10"
ISO_URL="https://sjc.mirror.rackspace.com/rocky/10/isos/x86_64/Rocky-10.1-x86_64-dvd1.iso"
ISO_HASH="55f96d45a052c0ed4f06309480155cb66281a008691eb7f3f359957205b1849a"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant rocky9 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/rockylinux8.sh
================================================
#!/bin/bash
set -ev
NAME="rockylinux8"
ISO_URL="https://sjc.mirror.rackspace.com/rocky/8/isos/x86_64/Rocky-8.10-x86_64-dvd1.iso"
ISO_HASH="642ada8a49dbeca8cca6543b31196019ee3d649a0163b5db0e646c7409364eeb"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant rocky8 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/rockylinux9.sh
================================================
#!/bin/bash
set -ev
NAME="rockylinux9"
ISO_URL="https://sjc.mirror.rackspace.com/rocky/9/isos/x86_64/Rocky-9.7-x86_64-dvd.iso"
ISO_HASH="d48e902325dce6793935b4e13672a0d9a4f958e02d4e23fcf0a8a34c49ef03da"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo tee /var/lib/virt/ks/${NAME}.ks << EOF
text
cdrom
%addon com_redhat_kdump --disable
%end
keyboard --xlayouts='us'
lang en_US.UTF-8
network --bootproto=dhcp --hostname=cloud --activate
%packages
@^minimal-environment
@standard
-kexec-tools
%end
firstboot --enable
ignoredisk --only-use=vda
clearpart --all --initlabel
part /boot/efi --fstype="efi" --ondisk=vda --size=100 --fsoptions="umask=0077,shortname=winnt"
part / --fstype="xfs" --ondisk=vda --grow
timezone Etc/UTC --utc
rootpw --plaintext cloud
%post
grubby --update-kernel=ALL --remove-args=crashkernel
%end
EOF
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--os-variant rocky9 \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}) \
--initrd-inject=/var/lib/virt/ks/${NAME}.ks \
--extra-args="console=ttyS0 inst.ks=file:/${NAME}.ks inst.text"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/ubuntu24.sh
================================================
#!/bin/bash
set -ev
NAME="ubuntu2404"
ISO_URL="https://cofractal-sea.mm.fcix.net/ubuntu-releases/24.04/ubuntu-24.04.3-live-server-amd64.iso"
ISO_HASH="c3514bf0056180d09376462a7a1b4f213c1d6e8ea67fae5c25099c6fd3d8274b"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo mkdir -p /var/lib/virt/init
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo mkdir /var/lib/virt/init/${NAME}
sudo tee /var/lib/virt/init/${NAME}/user-data << EOF
#cloud-config
autoinstall:
version: 1
timezone: "Etc/UTC"
identity:
realname: "Cloud"
username: cloud
password: "\$6\$x7YEknTyUuNSTTVK\$nq4xoSTrYp7a/Kb1EvtpH97GxG02CFBqELznybQv4XrA7sskq9PI0Y5KADhp9KiwVdrwR6v2IP6wqoxyXj4SP/"
hostname: cloud
storage:
layout:
name: direct
config:
- type: disk
id: disk0
match:
size: largest
- type: partition
id: efi-partition
device: disk0
size: 100M
flag: boot
grub_device: true
- type: partition
id: root-partition
device: disk0
size: -1
- type: format
id: efi-format
volume: efi-partition
fstype: fat32
- type: format
id: root-format
volume: root-partition
fstype: xfs
- type: mount
id: efi-mount
device: efi-format
path: /boot/efi
- type: mount
id: root-mount
device: root-format
path: /
EOF
sudo tee /var/lib/virt/init/${NAME}/meta-data << EOF
instance-id: ${NAME}
local-hostname: cloud
EOF
sudo rm -f /var/lib/virt/init/${NAME}.iso
sudo xorriso -as mkisofs \
-output /var/lib/virt/init/${NAME}.iso \
-volid cidata \
-joliet \
-rock \
-input-charset utf-8 \
/var/lib/virt/init/${NAME}/user-data \
/var/lib/virt/init/${NAME}/meta-data
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--disk path=/var/lib/virt/init/${NAME}.iso,device=cdrom \
--os-variant ubuntu-lts-latest \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=casper/hwe-vmlinuz,initrd=casper/hwe-initrd \
--extra-args="console=ttyS0 serial autoinstall"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
sudo rm -rf /var/lib/virt/init/${NAME}.iso
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/install/ubuntu26.sh
================================================
#!/bin/bash
set -ev
NAME="ubuntu2604"
ISO_URL="https://cofractal-sea.mm.fcix.net/ubuntu-releases/26.04/ubuntu-26.04-live-server-amd64.iso"
ISO_HASH="dec49008a71f6098d0bcfc822021f4d042d5f2db279e4d75bdd981304f1ca5d9"
sudo mkdir -p /var/lib/virt/iso
sudo mkdir -p /var/lib/virt/ks
sudo mkdir -p /var/lib/virt/images
sudo mkdir -p /var/lib/virt/init
sudo virsh destroy ${NAME} || true
sudo virsh undefine ${NAME} --nvram || true
sudo rm -f /var/lib/virt/${NAME}.qcow2
if [ ! -f "/var/lib/virt/iso/$(basename ${ISO_URL})" ]; then
sudo wget -P /var/lib/virt/iso ${ISO_URL}
fi
echo "${ISO_HASH} /var/lib/virt/iso/$(basename ${ISO_URL})" | sha256sum --check
if [ $? -ne 0 ]; then
echo "Checksum for ISO failed"
exit 1
fi
sudo mkdir /var/lib/virt/init/${NAME}
sudo tee /var/lib/virt/init/${NAME}/user-data << EOF
#cloud-config
autoinstall:
version: 1
timezone: "Etc/UTC"
identity:
realname: "Cloud"
username: cloud
password: "\$6\$x7YEknTyUuNSTTVK\$nq4xoSTrYp7a/Kb1EvtpH97GxG02CFBqELznybQv4XrA7sskq9PI0Y5KADhp9KiwVdrwR6v2IP6wqoxyXj4SP/"
hostname: cloud
storage:
layout:
name: direct
config:
- type: disk
id: disk0
match:
size: largest
- type: partition
id: efi-partition
device: disk0
size: 100M
flag: boot
grub_device: true
- type: partition
id: root-partition
device: disk0
size: -1
- type: format
id: efi-format
volume: efi-partition
fstype: fat32
- type: format
id: root-format
volume: root-partition
fstype: xfs
- type: mount
id: efi-mount
device: efi-format
path: /boot/efi
- type: mount
id: root-mount
device: root-format
path: /
EOF
sudo tee /var/lib/virt/init/${NAME}/meta-data << EOF
instance-id: ${NAME}
local-hostname: cloud
EOF
sudo rm -f /var/lib/virt/init/${NAME}.iso
sudo xorriso -as mkisofs \
-output /var/lib/virt/init/${NAME}.iso \
-volid cidata \
-joliet \
-rock \
-input-charset utf-8 \
/var/lib/virt/init/${NAME}/user-data \
/var/lib/virt/init/${NAME}/meta-data
sudo virt-install \
--name ${NAME} \
--vcpus 8 \
--memory 8192 \
--boot uefi \
--disk path=/var/lib/virt/${NAME}.qcow2,size=8,format=qcow2,bus=virtio \
--disk path=/var/lib/virt/init/${NAME}.iso,device=cdrom \
--os-variant ubuntu-lts-latest \
--network network=default \
--graphics=none \
--console pty,target_type=serial \
--location=/var/lib/virt/iso/$(basename ${ISO_URL}),kernel=casper/vmlinuz,initrd=casper/initrd \
--extra-args="console=ttyS0 serial autoinstall"
while ! sudo virsh domstate ${NAME} 2>/dev/null | grep -q "shut off"; do
sleep 1
done
sudo rm -rf /var/lib/virt/init/${NAME}
sudo rm -rf /var/lib/virt/init/${NAME}.iso
echo "Compressing image..."
sudo rm -f /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sudo qemu-img convert -f qcow2 -O qcow2 -c /var/lib/virt/${NAME}.qcow2 /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
sha256sum /var/lib/virt/images/${NAME}_$(date +%y%m%d).qcow2
================================================
FILE: tools/virt-install/setup/alpine.sh
================================================
#!/bin/sh
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting alpine setup
#############################################################
tee /etc/motd << EOF
Welcome to Alpine!
The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See .
EOF
echo "iso9660" > /etc/filesystems
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub-mkconfig -o /boot/grub/grub.cfg
rc-update add sshd default
rc-update add chronyd default
rc-update add qemu-guest-agent default
setup-cloud-init
tee /etc/init.d/cloud-fix << EOF
#!/sbin/openrc-run
description="cloud-init final fix stage"
depend() {
after cloud-config
provide cloud-fix
}
start() {
if grep -q 'cloud-init=disabled' /proc/cmdline; then
ewarn "\$RC_SVCNAME is disabled via /proc/cmdline."
elif test -e /etc/cloud/cloud-init.disabled; then
ewarn "\$RC_SVCNAME is disabled via cloud-init.disabled file"
else
ebegin "cloud-init fix"
cloud-init modules --mode final
eend \$?
fi
}
EOF
chmod +x /etc/init.d/cloud-fix
rc-update add cloud-fix default
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
mkdir -p /home/alpine
chown alpine:alpine /home/alpine
chmod 700 /home/alpine
usermod -l cloud alpine
usermod -m -d /home/cloud cloud
groupmod -n cloud alpine
usermod -aG adm,wheel cloud
passwd -d root
passwd -l root
passwd -d cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
cloud_password=$(tr -dc 'A-Za-z0-9!@#$%^&*()_+~' < /dev/urandom | head -c 64)
echo "cloud:$cloud_password" | chpasswd
passwd -u cloud
cloud-init clean --machine-id
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.ash_history || true
shred -u /home/cloud/.ash_history || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
sync
fstrim -v /
sync
#############################################################
# finished alpine setup, clear history and shutdown:
# unset HISTFILE && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/arch.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting arch setup
#############################################################
sed -i 's/^timeout.*/timeout 0/' /boot/loader/loader.conf
pacman -Syu
pacman -Sy --noconfirm chrony qemu-guest-agent cloud-init cloud-guest-utils dhcpcd
systemctl enable sshd
systemctl enable chronyd
systemctl enable cloud-init-local
systemctl enable cloud-init-main
systemctl enable cloud-config
systemctl enable cloud-final
systemctl disable systemd-networkd-wait-online
systemctl mask systemd-networkd-wait-online
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
useradd -m -G wheel,systemd-journal cloud
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
cloud-init clean --machine-id
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub 2>/dev/null || true
shred -u /root/.ssh/authorized_keys 2>/dev/null || true
shred -u /root/.bash_history 2>/dev/null || true
shred -u /home/cloud/.bash_history 2>/dev/null || true
shred -u /var/log/lastlog 2>/dev/null || true
shred -u /var/log/secure 2>/dev/null || true
shred -u /var/log/utmp 2>/dev/null || true
shred -u /var/log/wtmp 2>/dev/null || true
shred -u /var/log/btmp 2>/dev/null || true
shred -u /var/log/dmesg 2>/dev/null || true
shred -u /var/log/dmesg.old 2>/dev/null || true
shred -u /var/lib/systemd/random-seed 2>/dev/null || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
sync
fstrim -v /
sync
#############################################################
# finished arch setup, clear history and shutdown:
# unset HISTFILE && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/debian.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting debian setup
#############################################################
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
apt update
apt -y upgrade
apt -y autoremove
apt -y install bash-completion qemu-guest-agent cloud-init cloud-initramfs-growroot chrony openssh-server
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
systemctl enable cloud-init-local.service
if systemctl list-unit-files cloud-init-main.service >/dev/null 2>&1; then
systemctl enable cloud-init-main.service
else
systemctl enable cloud-init.service
fi
systemctl enable cloud-config.service
systemctl enable cloud-final.service
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%sudo/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%sudo ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable ssh.service
ufw disable
apt clean
rm -f /etc/cloud/cloud.cfg.d/90-installer-network.cfg
rm -f /etc/cloud/cloud.cfg.d/99-installer.cfg
cloud-init clean --machine-id
rm -rf /etc/NetworkManager/system-connections/*
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished debian setup, clear history and shutdown:
# unset HISTFILE && history -c && sudo poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/fedora.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting fedora setup
#############################################################
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
dnf clean all
dnf -y update
dnf -y install bash-completion qemu-guest-agent dnf-utils cloud-init cloud-utils-growpart chrony openssh-server
dnf -y update
dnf -y remove cockpit-ws
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
# cloud-init fix
if [[ "$(cloud-init --version 2>&1)" == *"25.2"* ]]; then
wget https://dl.fedoraproject.org/pub/fedora/linux/releases/44/Everything/x86_64/os/Packages/c/cloud-init-25.3-3.fc44.noarch.rpm
echo "877c3b272f2202d46a7c1d06185eea262feb5bda637fa49575ae8c9a96e62652 cloud-init-25.3-3.fc44.noarch.rpm" | dnf -y install cloud-init-25.3-3.fc44.noarch.rpm
rm -f cloud-init-25.3-3.fc44.noarch.rpm
fi
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
systemctl enable cloud-init-local.service
if systemctl list-unit-files cloud-init-main.service >/dev/null 2>&1; then
systemctl enable cloud-init-main.service
else
systemctl enable cloud-init.service
fi
systemctl enable cloud-config.service
systemctl enable cloud-final.service
sed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/dnf/dnf.conf
sed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
restorecon -v /etc/ssh/trusted
restorecon -v /etc/ssh/principals
useradd -m -G adm,video,wheel,systemd-journal cloud || true
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
restorecon -v /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
restorecon -v /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable sshd
systemctl restart sshd
systemctl disable firewalld
systemctl stop firewalld
systemctl start chronyd
systemctl enable chronyd
dnf clean all
rm -rf /var/cache/dnf
cloud-init clean --machine-id
rm -rf /etc/NetworkManager/system-connections/*
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished fedora setup, clear history and shutdown:
# unset HISTFILE && history -c && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/freebsd.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting freebsd setup
#############################################################
env PAGER=/bin/cat freebsd-update fetch
freebsd-update install || true
env ASSUME_ALWAYS_YES=yes pkg update
pkg upgrade -y
sysrc -f /boot/loader.conf autoboot_delay=0
sysrc ifconfig_vtnet0=""
sysrc ifconfig_vtnet0_ipv6=""
pkg search cloud-init
pkg install -y dual-dhclient py311-cloud-init
sysrc dhclient_program="/usr/local/sbin/dual-dhclient"
pw mod user root -w no
pw mod user cloud -w no
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i "" '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i "" '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i "" '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i "" '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i "" '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i "" '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
tee /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
chmod 600 /etc/sudoers
sysrc swapoff="YES"
sysrc ifconfig_vtnet0=""
sysrc cloudinit_enable="YES"
tee /usr/local/etc/cloud/cloud.cfg.d/99_cloud.cfg << EOF
datasource_list: [ NoCloud ]
EOF
cloud-init clean --machine-id
tee /usr/local/etc/rc.d/cloudinitfix << EOF
#!/bin/sh
# PROVIDE: cloudinitfix
# REQUIRE: FILESYSTEMS NETWORKING ldconfig devd
# BEFORE: LOGIN cloudconfig cloudinit
. /etc/rc.subr
PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
name="cloudinitfix"
start_cmd="cloudinitfix_start"
stop_cmd=":"
rcvar="cloudinit_enable"
cloudinitfix_start()
{
rm -rf /var/lib/cloud/instances
}
load_rc_config \$name
: \${cloudinitfix_enable="NO"}
run_rc_command "\$1"
EOF
chmod 755 /usr/local/etc/rc.d/cloudinitfix
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
rm -f /etc/resolv.conf.bak
sync
rm -f /var/db/dhclient.leases.vtnet0
rm -f /var/db/dhclient6.leases
rm -f setup.sh
rm -rf /root/.cache
rm -rf /home/cloud/.cache
rm -f /etc/ssh/*_key*
rm -f /root/.ssh/authorized_keys
rm -f /root/.history
rm -f /home/cloud/.history
rm -f /root/.bash_history
rm -f /home/cloud/.bash_history
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
sync
#############################################################
# finished freebsd setup, clear history and shutdown:
# unset history && unset HISTFILE && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/rhel10.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting rhel10 setup
#############################################################
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
truncate -s 0 /etc/yum/vars/ociregion || true
dnf clean all
dnf -y update
dnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server
dnf -y remove cockpit-ws
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
sed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf
sed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
restorecon -v /etc/ssh/trusted
restorecon -v /etc/ssh/principals
useradd -m -G adm,video,wheel,systemd-journal cloud || true
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
restorecon -v /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
restorecon -v /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable sshd
systemctl restart sshd
systemctl disable firewalld
systemctl stop firewalld
systemctl start chronyd
systemctl enable chronyd
dnf clean all
rm -rf /var/cache/dnf
cloud-init clean --machine-id
rm -rf /etc/NetworkManager/system-connections/*
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished rhel10 setup, clear history and shutdown:
# unset HISTFILE && history -c && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/rhel7.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting rhel7 setup
#############################################################
yum-config-manager --disable ol7_ociyum_config || true
truncate -s 0 /etc/yum/vars/ociregion || true
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
yum clean all
yum -y update
yum -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server
yum -y remove cockpit-ws
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
rm -f /etc/sysconfig/network-scripts/ifcfg-eth*
sed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf
sed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
restorecon -v /etc/ssh/trusted
restorecon -v /etc/ssh/principals
useradd -m -G adm,video,wheel,systemd-journal cloud || true
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
restorecon -v /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
restorecon -v /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable sshd
systemctl restart sshd
systemctl disable firewalld
systemctl stop firewalld
systemctl start chronyd
systemctl enable chronyd
yum clean all
rm -rf /var/cache/yum
cloud-init clean
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished rhel7 setup, clear history and shutdown:
# unset HISTFILE && history -c && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/rhel8.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting rhel8 setup
#############################################################
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
truncate -s 0 /etc/yum/vars/ociregion || true
dnf clean all
dnf -y update
dnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server
dnf -y remove cockpit-ws
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
rm -f /etc/sysconfig/network-scripts/ifcfg-eth*
sed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf
sed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
restorecon -v /etc/ssh/trusted
restorecon -v /etc/ssh/principals
useradd -m -G adm,video,wheel,systemd-journal cloud || true
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
restorecon -v /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
restorecon -v /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable sshd
systemctl restart sshd
systemctl disable firewalld
systemctl stop firewalld
systemctl start chronyd
systemctl enable chronyd
cloud-init clean --machine-id
rm -rf /etc/NetworkManager/system-connections/*
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
dnf clean all
rm -rf /var/cache/dnf
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished rhel8 setup, clear history and shutdown:
# unset HISTFILE && history -c && poweroff
#############################################################
================================================
FILE: tools/virt-install/setup/rhel9.sh
================================================
#!/bin/bash
set -ev
if [ $(whoami) != "root" ]; then
echo "Must be run as root"
exit 1
fi
#############################################################
# starting rhel9 setup
#############################################################
tee /etc/modprobe.d/floppy-blacklist.conf << EOF
blacklist floppy
EOF
truncate -s 0 /etc/yum/vars/ociregion || true
dnf clean all
dnf -y update
dnf -y install bash-completion qemu-guest-agent cloud-init cloud-utils-growpart chrony openssh-server
dnf -y remove cockpit-ws
sed -i 's/^GRUB_TIMEOUT=.*/GRUB_TIMEOUT=0/g' /etc/default/grub
grub2-mkconfig -o /boot/grub2/grub.cfg
systemctl daemon-reload
systemctl enable qemu-guest-agent.service
sed -i 's/^installonly_limit=.*/installonly_limit=2/g' /etc/yum.conf
sed -i 's/^SELINUX=.*/SELINUX=enforcing/g' /etc/selinux/config || true
sed -i '/^PermitRootLogin/d' /etc/ssh/sshd_config
sed -i '/^PasswordAuthentication/d' /etc/ssh/sshd_config
sed -i '/^ChallengeResponseAuthentication/d' /etc/ssh/sshd_config
sed -i '/^KbdInteractiveAuthentication/d' /etc/ssh/sshd_config
sed -i '/^TrustedUserCAKeys/d' /etc/ssh/sshd_config
sed -i '/^AuthorizedPrincipalsFile/d' /etc/ssh/sshd_config
tee -a /etc/ssh/sshd_config << EOF
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
TrustedUserCAKeys /etc/ssh/trusted
AuthorizedPrincipalsFile /etc/ssh/principals
EOF
touch /etc/ssh/trusted
touch /etc/ssh/principals
restorecon -v /etc/ssh/trusted
restorecon -v /etc/ssh/principals
useradd -m -G adm,video,wheel,systemd-journal cloud || true
passwd -d root
passwd -l root
passwd -d cloud
passwd -l cloud
mkdir -p /home/cloud/.ssh
chown cloud:cloud /home/cloud/.ssh
restorecon -v /home/cloud/.ssh
touch /home/cloud/.ssh/authorized_keys
chown cloud:cloud /home/cloud/.ssh/authorized_keys
restorecon -v /home/cloud/.ssh/authorized_keys
chmod 700 /home/cloud/.ssh
chmod 600 /home/cloud/.ssh/authorized_keys
sed -i '/^%wheel/d' /etc/sudoers
tee -a /etc/sudoers << EOF
%wheel ALL=(ALL) NOPASSWD:ALL
EOF
systemctl enable sshd
systemctl restart sshd
systemctl disable firewalld
systemctl stop firewalld
systemctl start chronyd
systemctl enable chronyd
dnf clean all
rm -rf /var/cache/dnf
cloud-init clean --machine-id
rm -rf /etc/NetworkManager/system-connections/*
tee /etc/resolv.conf << EOF
nameserver 8.8.8.8
nameserver 8.8.4.4
EOF
sync
sleep 1
find /var/log -mtime -1 -type f -exec truncate -s 0 {} \;
rm -rf /var/tmp/dnf-*
rm -rf /home/cloud/.cache
shred -u /etc/ssh/*_key /etc/ssh/*_key.pub || true
shred -u /root/.ssh/authorized_keys || true
shred -u /root/.bash_history || true
shred -u /home/cloud/.bash_history || true
shred -u /var/log/lastlog || true
shred -u /var/log/secure || true
shred -u /var/log/utmp || true
shred -u /var/log/wtmp || true
shred -u /var/log/btmp || true
shred -u /var/log/dmesg || true
shred -u /var/log/dmesg.old || true
shred -u /var/lib/systemd/random-seed || true
rm -rf /var/log/*.gz
rm -rf /var/log/*.[0-9]
rm -rf /var/log/*-????????
rm -rf /var/lib/cloud/instances/*
rm -f /var/lib/systemd/random-seed
rm -f /etc/machine-id
touch /etc/machine-id
sync
fstrim -av
sync
#############################################################
# finished rhel9 setup, clear history and shutdown:
# unset HISTFILE && history -c && poweroff
#############################################################
================================================
FILE: tools/webpack_run.sh
================================================
#!/bin/bash
set -e
npx webpack-cli --config webpack.dev.config --progress --color --watch
================================================
FILE: tpm/tpm.go
================================================
package tpm
import (
"fmt"
"os"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/permission"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
const systemdTemplate = `[Unit]
Description=Pritunl Cloud TPM
After=network.target
[Service]
Type=simple
User=%s
ExecStart=swtpm socket --tpm2 --key pwdfile=%s,mode=aes-256-cbc,remove=true,kdf=pbkdf2 --tpmstate dir=%s --ctrl type=unixio,path=%s --log level=5
TimeoutStopSec=5
PrivateTmp=true
ProtectHome=true
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
NetworkNamespacePath=/var/run/netns/%s
`
func WriteService(vmId bson.ObjectID, namespace string) (err error) {
unitPath := paths.GetUnitPathTpm(vmId)
tpmPath := paths.GetTpmPath(vmId)
pwdPath := paths.GetTpmPwdPath(vmId)
sockPath := paths.GetTpmSockPath(vmId)
output := fmt.Sprintf(
systemdTemplate,
permission.GetUserName(vmId),
pwdPath,
tpmPath,
sockPath,
namespace,
)
err = utils.CreateWrite(unitPath, output, 0644)
if err != nil {
return
}
return
}
func Start(db *database.Database, virt *vm.VirtualMachine) (err error) {
namespace := vm.GetNamespace(virt.Id, 0)
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
}).Info("tpm: Starting virtual machine tpm")
tpmsPath := paths.GetTpmsPath()
tpmPath := paths.GetTpmPath(virt.Id)
unit := paths.GetUnitNameTpm(virt.Id)
pwdPath := paths.GetTpmPwdPath(virt.Id)
err = utils.ExistsMkdir(tpmsPath, 0755)
if err != nil {
return
}
err = utils.ExistsMkdir(tpmPath, 0700)
if err != nil {
return
}
err = permission.InitTpm(virt)
if err != nil {
return
}
_ = systemd.Stop(unit)
secret, err := GetSecret(db, virt.Id)
if err != nil {
return
}
if secret == "" {
err = &errortypes.NotFoundError{
errors.New("tpm: Missing instance tpm secret"),
}
return
}
err = WriteService(virt.Id, namespace)
if err != nil {
return
}
err = systemd.Reload()
if err != nil {
return
}
go func() {
time.Sleep(15 * time.Second)
_ = os.Remove(pwdPath)
}()
err = utils.CreateWrite(pwdPath, secret, 0600)
if err != nil {
return
}
err = permission.InitTpmPwd(virt)
if err != nil {
_ = os.Remove(pwdPath)
return
}
err = systemd.Start(unit)
if err != nil {
_ = os.Remove(pwdPath)
return
}
return
}
func Stop(virt *vm.VirtualMachine) (err error) {
unit := paths.GetUnitNameTpm(virt.Id)
_ = systemd.Stop(unit)
return
}
================================================
FILE: tpm/utils.go
================================================
package tpm
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
type instanceData struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
TpmSecret string `bson:"tpm_secret" json:"-"`
}
func GenerateSecret() (secret string, err error) {
secret, err = utils.RandPasswd(128)
if err != nil {
return
}
return
}
func GetSecret(db *database.Database, vmId bson.ObjectID) (
secret string, err error) {
coll := db.Instances()
data := &instanceData{}
err = coll.FindOne(
db,
&bson.M{
"_id": vmId,
},
options.FindOne().
SetProjection(bson.D{{"tpm_secret", 1}}),
).Decode(data)
secret = data.TpmSecret
return
}
================================================
FILE: twilio/twilio.go
================================================
package twilio
import (
"encoding/xml"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/sirupsen/logrus"
"github.com/twilio/twilio-go"
openapi "github.com/twilio/twilio-go/rest/api/v2010"
)
type TwimlSay struct {
XMLName xml.Name `xml:"Say"`
Voice string `xml:"voice,attr"`
Loop string `xml:"loop,attr"`
Message string `xml:",chardata"`
}
type TwimlResponse struct {
XMLName xml.Name `xml:"Response"`
Say *TwimlSay `xml:"Say"`
}
func PhoneCall(number, message string) (err error) {
client := twilio.NewRestClientWithParams(twilio.ClientParams{
Username: settings.System.TwilioAccount,
Password: settings.System.TwilioSecret,
})
params := &openapi.CreateCallParams{}
params.SetFrom(settings.System.TwilioNumber)
params.SetTo(number)
twiml := &TwimlResponse{
Say: &TwimlSay{
Voice: "alice",
Loop: "3",
Message: FilterStrPhone(message, 160),
},
}
twimlData, err := xml.Marshal(twiml)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "twilio: Failed to marshal twiml message"),
}
return
}
params.SetTwiml(string(twimlData))
resp, err := client.Api.CreateCall(params)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "twilio: Twilio call error"),
}
return
}
respSid := *resp.Sid
if respSid == "" {
err = &errortypes.RequestError{
errors.Wrap(err, "twilio: Invalid call sid"),
}
return
}
return
}
func TextMessage(number, message string) (err error) {
client := twilio.NewRestClientWithParams(twilio.ClientParams{
Username: settings.System.TwilioAccount,
Password: settings.System.TwilioSecret,
})
params := &openapi.CreateMessageParams{}
params.SetFrom(settings.System.TwilioNumber)
params.SetTo(number)
params.SetBody("Pritunl Alert: " + FilterStrMessage(message, 800))
resp, err := client.Api.CreateMessage(params)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "twilio: Twilio message error"),
}
return
}
respSid := *resp.Sid
if respSid == "" {
err = &errortypes.RequestError{
errors.Wrap(err, "twilio: Invalid message sid"),
}
return
}
if resp.ErrorCode != nil && resp.ErrorMessage != nil &&
*resp.ErrorMessage != "" {
logrus.WithFields(logrus.Fields{
"number": number,
"message": message,
"source_number": settings.System.TwilioNumber,
"error_code": resp.ErrorCode,
"error_message": resp.ErrorMessage,
}).Error("twilio: Text message error")
err = &errortypes.RequestError{
errors.Wrap(err, "twilio: Twilio message error"),
}
return
}
return
}
================================================
FILE: twilio/utils.go
================================================
package twilio
import (
"github.com/dropbox/godropbox/container/set"
)
var safeCharsPhone = set.NewSet(
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'-',
'+',
'=',
'_',
'/',
',',
'.',
':',
'%',
'@',
'!',
' ',
)
var safeCharsMessage = set.NewSet(
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'-',
'+',
'=',
'_',
'/',
',',
'.',
':',
'#',
'@',
'%',
'!',
'[',
']',
'(',
')',
' ',
)
func filterStr(s string, n int, safe set.Set) string {
if len(s) == 0 {
return ""
}
if len(s) > n {
s = s[:n]
}
ns := ""
for _, c := range s {
if safe.Contains(c) {
ns += string(c)
}
}
return ns
}
func FilterStrPhone(s string, n int) string {
return filterStr(s, n, safeCharsPhone)
}
func FilterStrMessage(s string, n int) string {
return filterStr(s, n, safeCharsMessage)
}
================================================
FILE: uhandlers/alert.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/alert"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
)
type alertData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Roles []string `json:"roles"`
Resource string `json:"resource"`
Level int `json:"level"`
Frequency int `bson:"frequency" json:"frequency"`
Ignores []string `bson:"ignores" json:"ignores"`
ValueInt int `json:"value_int"`
ValueStr string `json:"value_str"`
}
type alertsData struct {
Alerts []*alert.Alert `json:"alerts"`
Count int64 `json:"count"`
}
func alertPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &alertData{}
alertId, ok := utils.ParseObjectId(c.Param("alert_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
alrt, err := alert.GetOrg(db, userOrg, alertId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
alrt.Name = data.Name
alrt.Comment = data.Comment
alrt.Roles = data.Roles
alrt.Resource = data.Resource
alrt.Level = data.Level
alrt.Frequency = data.Frequency
alrt.Ignores = data.Ignores
alrt.ValueInt = data.ValueInt
alrt.ValueStr = data.ValueStr
fields := set.NewSet(
"name",
"comment",
"roles",
"resource",
"level",
"frequency",
"ignores",
"value_int",
"value_str",
)
errData, err := alrt.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = alrt.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_ = event.PublishDispatch(db, "alert.change")
c.JSON(200, alrt)
}
func alertPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &alertData{
Name: "new-alert",
Resource: alert.InstanceOffline,
Level: alert.Medium,
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
alrt := &alert.Alert{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
Roles: data.Roles,
Resource: data.Resource,
Level: data.Level,
Frequency: data.Frequency,
Ignores: data.Ignores,
ValueInt: data.ValueInt,
ValueStr: data.ValueStr,
}
errData, err := alrt.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = alrt.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_ = event.PublishDispatch(db, "alert.change")
c.JSON(200, alrt)
}
func alertDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
alertId, ok := utils.ParseObjectId(c.Param("alert_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := alert.RemoveOrg(db, userOrg, alertId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_ = event.PublishDispatch(db, "alert.change")
c.JSON(200, nil)
}
func alertsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := []bson.ObjectID{}
err := c.Bind(&dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = alert.RemoveMultiOrg(db, userOrg, dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_ = event.PublishDispatch(db, "alert.change")
c.JSON(200, nil)
}
func alertGet(c *gin.Context) {
if demo.IsDemo() {
alrt := demo.Alerts[0]
c.JSON(200, alrt)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
alertId, ok := utils.ParseObjectId(c.Query("id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
alrt, err := alert.GetOrg(db, userOrg, alertId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, alrt)
}
func alertsGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
alertId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = alertId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["$or"] = []*bson.M{
&bson.M{
"name": &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
},
},
}
}
role := strings.TrimSpace(c.Query("role"))
if role != "" {
if strings.HasPrefix(role, "~") {
role := role[1:]
if strings.HasPrefix(role, "!") {
query["roles"] = &bson.M{
"$not": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role[1:])),
"$options": "i",
},
}
} else {
query["$or"] = []*bson.M{
&bson.M{
"roles": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role)),
"$options": "i",
},
},
}
}
} else {
if strings.HasPrefix(role, "!") {
role = strings.TrimLeft(role, "!")
query["roles"] = &bson.M{
"$ne": role,
}
} else {
query["roles"] = role
}
}
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
alerts, count, err := alert.GetAllPaged(
db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
dta := &alertsData{
Alerts: alerts,
Count: count,
}
c.JSON(200, dta)
}
================================================
FILE: uhandlers/auth.go
================================================
package uhandlers
import (
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/auth"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/cookie"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/device"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/secondary"
"github.com/pritunl/pritunl-cloud/session"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/validator"
)
func authStateGet(c *gin.Context) {
data := auth.GetState()
if demo.IsDemo() {
provider := &auth.StateProvider{
Id: "demo",
Type: "demo",
Label: "demo",
}
data.Providers = append(data.Providers, provider)
}
c.JSON(200, data)
}
type authData struct {
Username string `json:"username"`
Password string `json:"password"`
}
func authSessionPost(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
data := &authData{}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
usr, errData, err := auth.Local(db, data.Username, data.Password)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(401, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserPrimaryApprove,
audit.Fields{
"method": "local",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devAuth, secProviderId, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "local"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
if devAuth {
deviceCount, err := device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secType := ""
var secProvider bson.ObjectID
if deviceCount == 0 {
if secProviderId.IsZero() {
secType = secondary.UserDeviceRegister
secProvider = secondary.DeviceProvider
} else {
secType = secondary.User
secProvider = secProviderId
}
} else {
secType = secondary.UserDevice
secProvider = secondary.DeviceProvider
}
secd, err := secondary.New(db, usr.Id, secType, secProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
} else if !secProviderId.IsZero() {
secd, err := secondary.New(db, usr.Id, secondary.User, secProviderId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogin,
audit.Fields{
"method": "local",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cook := cookie.NewUser(c.Writer, c.Request)
_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
redirectQueryJson(c, c.Request.URL.RawQuery)
}
type secondaryData struct {
Token string `json:"token"`
Factor string `json:"factor"`
Passcode string `json:"passcode"`
}
func authSecondaryPost(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
data := &secondaryData{}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token, secondary.User)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(401, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := secd.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
errData, err := secd.Handle(db, c.Request, data.Factor, data.Passcode)
if err != nil {
if _, ok := err.(*secondary.IncompleteError); ok {
c.Status(206)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
if errData != nil {
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
audit.Fields{
"method": "secondary",
"provider_id": secd.ProviderId,
"error": errData.Error,
"message": errData.Message,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserSecondaryApprove,
audit.Fields{
"provider_id": secd.ProviderId,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
deviceAuth, _, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "secondary"
errAudit["provider_id"] = secd.ProviderId
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
if deviceAuth {
deviceCount, err := device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if deviceCount == 0 {
secd, err := secondary.New(db, usr.Id,
secondary.UserDeviceRegister, secondary.DeviceProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
}
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogin,
audit.Fields{
"method": "secondary",
"provider_id": secd.ProviderId,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cook := cookie.NewUser(c.Writer, c.Request)
_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
redirectQueryJson(c, c.Request.URL.RawQuery)
}
func logoutGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if authr.IsValid() {
err := authr.Remove(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
usr, _ := authr.GetUser(db)
if usr != nil {
err := audit.New(
db,
c.Request,
usr.Id,
audit.UserLogout,
audit.Fields{},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
c.Redirect(302, "/")
}
func logoutAllGet(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
sessions, err := session.GetAll(db, usr.Id, false)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, sess := range sessions {
err = sess.Remove(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
if authr.IsValid() {
err := authr.Remove(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogoutAll,
audit.Fields{},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.Redirect(302, "/")
}
func authRequestGet(c *gin.Context) {
auth.Request(c, auth.User)
}
func authCallbackGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
sig := c.Query("sig")
query := strings.Split(c.Request.URL.RawQuery, "&sig=")[0]
usr, tokn, errAudit, errData, err := auth.Callback(db, sig, query)
if err != nil {
switch err.(type) {
case *auth.InvalidState:
c.Redirect(302, "/")
break
default:
utils.AbortWithError(c, 500, err)
}
return
}
if errData != nil {
if usr != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "callback"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
c.JSON(401, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserPrimaryApprove,
audit.Fields{
"method": "callback",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devAuth, secProviderId, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "callback"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
if devAuth {
deviceCount, err := device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secType := ""
var secProvider bson.ObjectID
if deviceCount == 0 {
if secProviderId.IsZero() {
secType = secondary.UserDeviceRegister
secProvider = secondary.DeviceProvider
} else {
secType = secondary.User
secProvider = secProviderId
}
} else {
secType = secondary.UserDevice
secProvider = secondary.DeviceProvider
}
secd, err := secondary.New(db, usr.Id, secType, secProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
urlQuery, err := secd.GetQuery()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if tokn.Query != "" {
urlQuery += "&" + tokn.Query
}
c.Redirect(302, "/login?"+urlQuery)
return
} else if !secProviderId.IsZero() {
secd, err := secondary.New(db, usr.Id, secondary.User, secProviderId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
urlQuery, err := secd.GetQuery()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if tokn.Query != "" {
urlQuery += "&" + tokn.Query
}
c.Redirect(302, "/login?"+urlQuery)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogin,
audit.Fields{
"method": "callback",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cook := cookie.NewUser(c.Writer, c.Request)
_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
redirectQuery(c, tokn.Query)
}
func authU2fAppGet(c *gin.Context) {
c.JSON(200, device.GetFacets())
}
func authWanRegisterGet(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
token := c.Query("token")
if node.Self.WebauthnDomain == "" {
errData := &errortypes.ErrorData{
Error: "webauthn_domain_unavailable",
Message: "WebAuthn domain must be configured",
}
c.JSON(400, errData)
return
}
secd, err := secondary.Get(db, token, secondary.UserDeviceRegister)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(401, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := secd.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserDeviceRegisterRequest,
audit.Fields{},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
resp, errData, err := secd.DeviceRegisterRequest(db,
utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
audit.Fields{
"method": "device_register",
"error": errData.Error,
"message": errData.Message,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
c.JSON(200, resp)
}
type devicesRegisterData struct {
Token string `json:"token"`
Name string `json:"name"`
}
func authWanRegisterPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
data := &devicesRegisterData{}
body, err := utils.CopyBody(c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token, secondary.UserDeviceRegister)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(401, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := secd.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_, _, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "device_register"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
devc, errData, err := secd.DeviceRegisterResponse(
db, utils.GetOrigin(c.Request), body, data.Name)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = audit.New(
db,
c.Request,
usr.Id,
audit.DeviceRegisterFailed,
audit.Fields{
"error": errData.Error,
"message": errData.Message,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserDeviceRegister,
audit.Fields{
"device_id": devc.Id,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "device.change")
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogin,
audit.Fields{
"method": "device_register",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cook := cookie.NewUser(c.Writer, c.Request)
_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
redirectQueryJson(c, c.Request.URL.RawQuery)
}
func authWanRequestGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
token := c.Query("token")
secd, err := secondary.Get(db, token, secondary.UserDevice)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(401, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := secd.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
resp, errData, err := secd.DeviceRequest(
db, utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
audit.Fields{
"method": "device",
"error": errData.Error,
"message": errData.Message,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
c.JSON(200, resp)
}
type authWanRespondData struct {
Token string `json:"token"`
}
func authWanRespondPost(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
data := &authWanRespondData{}
body, err := utils.CopyBody(c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token, secondary.UserDevice)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(401, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := secd.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_, secProviderId, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "device"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
errData, err = secd.DeviceRespond(
db, utils.GetOrigin(c.Request), body)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLoginFailed,
audit.Fields{
"method": "device",
"error": errData.Error,
"message": errData.Message,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(401, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserDeviceApprove,
audit.Fields{},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !secProviderId.IsZero() {
secd, err := secondary.New(db, usr.Id, secondary.User,
secProviderId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserLogin,
audit.Fields{
"method": "device",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cook := cookie.NewUser(c.Writer, c.Request)
_, err = cook.NewSession(db, c.Request, usr.Id, true, session.User)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
redirectQueryJson(c, c.Request.URL.RawQuery)
}
================================================
FILE: uhandlers/authority.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/authority"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
)
type authorityData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Type string `json:"type"`
Roles []string `json:"roles"`
Key string `json:"key"`
Principals []string `json:"principals"`
Certificate string `json:"certificate"`
}
type authoritiesData struct {
Authorities []*authority.Authority `json:"authorities"`
Count int64 `json:"count"`
}
func authorityPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &authorityData{}
authorityId, ok := utils.ParseObjectId(c.Param("authority_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
authr, err := authority.GetOrg(db, userOrg, authorityId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
authr.Name = data.Name
authr.Comment = data.Comment
authr.Type = data.Type
authr.Roles = data.Roles
authr.Key = data.Key
authr.Principals = data.Principals
authr.Certificate = data.Certificate
fields := set.NewSet(
"name",
"comment",
"type",
"organization",
"roles",
"key",
"principals",
"certificate",
)
errData, err := authr.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = authr.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "authority.change")
c.JSON(200, authr)
}
func authorityPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &authorityData{
Name: "new-authority",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
fire := &authority.Authority{
Name: data.Name,
Comment: data.Comment,
Type: data.Type,
Organization: userOrg,
Roles: data.Roles,
Key: data.Key,
Principals: data.Principals,
Certificate: data.Certificate,
}
errData, err := fire.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = fire.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "authority.change")
c.JSON(200, fire)
}
func authorityDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
authorityId, ok := utils.ParseObjectId(c.Param("authority_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := authority.RemoveOrg(db, userOrg, authorityId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "authority.change")
c.JSON(200, nil)
}
func authoritiesDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = authority.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "authority.change")
c.JSON(200, nil)
}
func authorityGet(c *gin.Context) {
if demo.IsDemo() {
authr := demo.Authorities[0]
c.JSON(200, authr)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
authorityId, ok := utils.ParseObjectId(c.Param("authority_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
fire, err := authority.GetOrg(db, userOrg, authorityId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, fire)
}
func authoritiesGet(c *gin.Context) {
if demo.IsDemo() {
data := &authoritiesData{
Authorities: demo.Authorities,
Count: int64(len(demo.Authorities)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
authrId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = authrId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
role := strings.TrimSpace(c.Query("role"))
if role != "" {
if strings.HasPrefix(role, "~") {
role := role[1:]
if strings.HasPrefix(role, "!") {
query["roles"] = &bson.M{
"$not": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role[1:])),
"$options": "i",
},
}
} else {
query["$or"] = []*bson.M{
&bson.M{
"roles": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role)),
"$options": "i",
},
},
}
}
} else {
if strings.HasPrefix(role, "!") {
role = strings.TrimLeft(role, "!")
query["roles"] = &bson.M{
"$ne": role,
}
} else {
query["roles"] = role
}
}
}
principal := strings.TrimSpace(c.Query("principal"))
if principal != "" {
query["principals"] = principal
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
authorities, count, err := authority.GetAllPaged(
db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &authoritiesData{
Authorities: authorities,
Count: count,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/balancer.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/balancer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
)
type balancerData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
State bool `json:"state"`
Type string `json:"type"`
Datacenter bson.ObjectID `json:"datacenter"`
Certificates []bson.ObjectID `json:"certificates"`
WebSockets bool `json:"websockets"`
Domains []*balancer.Domain `json:"domains"`
Backends []*balancer.Backend `json:"backends"`
CheckPath string `json:"check_path"`
}
type balancersData struct {
Balancers []*balancer.Balancer `json:"balancers"`
Count int64 `json:"count"`
}
func balancerPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &balancerData{}
balancerId, ok := utils.ParseObjectId(c.Param("balancer_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
balnc, err := balancer.GetOrg(db, userOrg, balancerId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
balnc.Name = data.Name
balnc.Comment = data.Comment
balnc.State = data.State
balnc.Type = data.Type
balnc.Datacenter = data.Datacenter
balnc.Certificates = data.Certificates
balnc.WebSockets = data.WebSockets
balnc.Domains = data.Domains
balnc.Backends = data.Backends
balnc.CheckPath = data.CheckPath
exists, err := datacenter.ExistsOrg(db, userOrg, balnc.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
fields := set.NewSet(
"name",
"comment",
"state",
"type",
"datacenter",
"certificates",
"websockets",
"domains",
"backends",
"check_path",
)
errData, err := balnc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = balnc.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "balancer.change")
balnc.Json()
c.JSON(200, balnc)
}
func balancerPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &balancerData{
Name: "new-balancer",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
balnc := &balancer.Balancer{
Name: data.Name,
Comment: data.Comment,
State: data.State,
Type: data.Type,
Organization: userOrg,
Datacenter: data.Datacenter,
Certificates: data.Certificates,
WebSockets: data.WebSockets,
Domains: data.Domains,
Backends: data.Backends,
CheckPath: data.CheckPath,
}
exists, err := datacenter.ExistsOrg(db, userOrg, balnc.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
errData, err := balnc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = balnc.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "balancer.change")
balnc.Json()
c.JSON(200, balnc)
}
func balancerDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
balancerId, ok := utils.ParseObjectId(c.Param("balancer_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := balancer.RemoveOrg(db, userOrg, balancerId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "balancer.change")
c.JSON(200, nil)
}
func balancersDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = balancer.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "balancer.change")
c.JSON(200, nil)
}
func balancerGet(c *gin.Context) {
if demo.IsDemo() {
balnc := demo.Balancers[0]
c.JSON(200, balnc)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
balancerId, ok := utils.ParseObjectId(c.Param("balancer_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
balnc, err := balancer.GetOrg(db, userOrg, balancerId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
balnc.Json()
c.JSON(200, balnc)
}
func balancersGet(c *gin.Context) {
if demo.IsDemo() {
data := &balancersData{
Balancers: demo.Balancers,
Count: int64(len(demo.Balancers)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
balancerId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = balancerId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
datacenter, ok := utils.ParseObjectId(c.Query("datacenter"))
if ok {
query["datacenter"] = datacenter
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
balncs, count, err := balancer.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, balnc := range balncs {
balnc.Json()
}
data := &balancersData{
Balancers: balncs,
Count: count,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/certificate.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/acme"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/utils"
)
type certificateData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Type string `json:"type"`
Key string `json:"key"`
Certificate string `json:"certificate"`
AcmeDomains []string `json:"acme_domains"`
AcmeAuth string `json:"acme_auth"`
AcmeSecret bson.ObjectID `json:"acme_secret"`
Refresh bool `json:"refresh"`
}
type certificatesData struct {
Certificates []*certificate.Certificate `json:"certificates"`
Count int64 `json:"count"`
}
func certificatePut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &certificateData{}
certId, ok := utils.ParseObjectId(c.Param("cert_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cert, err := certificate.GetOrg(db, userOrg, certId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if cert.Type == certificate.LetsEncrypt &&
cert.AcmeType != certificate.AcmeDNS ||
cert.AcmeType == certificate.AcmeHTTP {
errData := &errortypes.ErrorData{
Error: "acme_type_blocked",
Message: "Cannot modify LetsEncrypt HTTP verified certificates",
}
c.JSON(400, errData)
return
}
if !data.AcmeSecret.IsZero() {
exists, err := secret.ExistsOrg(db, userOrg, data.AcmeSecret)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
} else {
data.AcmeSecret = bson.NilObjectID
}
cert.Name = data.Name
cert.Comment = data.Comment
cert.Key = data.Key
cert.Certificate = data.Certificate
cert.Type = data.Type
cert.AcmeDomains = data.AcmeDomains
cert.AcmeType = certificate.AcmeDNS
cert.AcmeAuth = data.AcmeAuth
cert.AcmeSecret = data.AcmeSecret
fields := set.NewSet(
"name",
"comment",
"type",
"acme_domains",
"acme_type",
"acme_auth",
"acme_secret",
"info",
)
if cert.Type != certificate.LetsEncrypt {
cert.Key = data.Key
fields.Add("key")
cert.Certificate = data.Certificate
fields.Add("certificate")
}
errData, err := cert.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = cert.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if cert.Type == certificate.LetsEncrypt {
acme.RenewBackground(cert, data.Refresh)
}
event.PublishDispatch(db, "certificate.change")
c.JSON(200, cert)
}
func certificatePost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &certificateData{
Name: "new-certificate",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
cert := &certificate.Certificate{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
Type: data.Type,
AcmeDomains: data.AcmeDomains,
AcmeType: certificate.AcmeDNS,
AcmeAuth: data.AcmeAuth,
AcmeSecret: data.AcmeSecret,
}
if cert.Type != certificate.LetsEncrypt {
cert.Key = data.Key
cert.Certificate = data.Certificate
}
if !cert.AcmeSecret.IsZero() {
_, err = secret.GetOrg(db, userOrg, cert.AcmeSecret)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
} else {
cert.AcmeSecret = bson.NilObjectID
}
errData, err := cert.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = cert.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if cert.Type == certificate.LetsEncrypt {
acme.RenewBackground(cert, false)
}
event.PublishDispatch(db, "certificate.change")
c.JSON(200, cert)
}
func certificateDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
certId, ok := utils.ParseObjectId(c.Param("cert_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := certificate.RemoveOrg(db, userOrg, certId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "certificate.change")
c.JSON(200, nil)
}
func certificatesDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
err = certificate.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "certificate.change")
c.JSON(200, nil)
}
func certificateGet(c *gin.Context) {
if demo.IsDemo() {
cert := demo.Certificates[0]
c.JSON(200, cert)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
if c.Query("names") == "true" {
certs, err := certificate.GetAllNames(db, &bson.M{
"organization": userOrg,
})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, certs)
return
}
certId, ok := utils.ParseObjectId(c.Param("cert_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
cert, err := certificate.GetOrg(db, userOrg, certId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if demo.IsDemo() {
cert.Key = "demo"
cert.AcmeAccount = "demo"
}
c.JSON(200, cert)
}
func certificatesGet(c *gin.Context) {
if demo.IsDemo() {
data := &certificatesData{
Certificates: demo.Certificates,
Count: int64(len(demo.Certificates)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
certificateId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = certificateId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
certs, count, err := certificate.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &certificatesData{
Certificates: certs,
Count: count,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/check.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
)
func checkGet(c *gin.Context) {
c.String(200, "ok")
}
================================================
FILE: uhandlers/completion.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/block"
"github.com/pritunl/pritunl-cloud/certificate"
"github.com/pritunl/pritunl-cloud/completion"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/organization"
"github.com/pritunl/pritunl-cloud/plan"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/shape"
"github.com/pritunl/pritunl-cloud/storage"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
)
func completionGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if demo.IsDemo() {
data := &completion.Completion{}
for _, item := range demo.Organizations {
data.Organizations = append(data.Organizations, &database.Named{
Id: item.Id,
Name: item.Name,
})
}
for _, item := range demo.Authorities {
data.Authorities = append(data.Authorities, &database.Named{
Id: item.Id,
Name: item.Name,
})
}
for _, item := range demo.Policies {
data.Policies = append(data.Policies, &database.Named{
Id: item.Id,
Name: item.Name,
})
}
for _, item := range demo.Domains {
data.Domains = append(data.Domains, &domain.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
})
}
for _, item := range demo.Vpcs {
data.Vpcs = append(data.Vpcs, &vpc.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
VpcId: item.VpcId,
Network: item.Network,
Subnets: item.Subnets,
Datacenter: item.Datacenter,
})
}
for _, item := range demo.Datacenters {
data.Datacenters = append(data.Datacenters, &datacenter.Completion{
Id: item.Id,
Name: item.Name,
NetworkMode: item.NetworkMode,
})
}
for _, item := range demo.Blocks {
data.Blocks = append(data.Blocks, &block.Completion{
Id: item.Id,
Name: item.Name,
Type: item.Type,
})
}
for _, item := range demo.Nodes {
data.Nodes = append(data.Nodes, &node.Completion{
Id: item.Id,
Name: item.Name,
Zone: item.Zone,
Types: item.Types,
})
}
for _, item := range demo.Pools {
data.Pools = append(data.Pools, &pool.Completion{
Id: item.Id,
Name: item.Name,
Zone: item.Zone,
})
}
for _, item := range demo.Zones {
data.Zones = append(data.Zones, &zone.Completion{
Id: item.Id,
Datacenter: item.Datacenter,
Name: item.Name,
})
}
for _, item := range demo.Shapes {
data.Shapes = append(data.Shapes, &shape.Completion{
Id: item.Id,
Name: item.Name,
Datacenter: item.Datacenter,
Flexible: item.Flexible,
Memory: item.Memory,
Processors: item.Processors,
})
}
imgs, err := image.GetAllCompletion(db, &bson.M{})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data.Images = imgs
for _, item := range demo.Storages {
data.Storages = append(data.Storages, &storage.Completion{
Id: item.Id,
Name: item.Name,
Type: item.Type,
})
}
for _, item := range demo.Instances {
data.Instances = append(data.Instances, &instance.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
Zone: item.Zone,
Vpc: item.Vpc,
Subnet: item.Subnet,
Node: item.Node,
})
}
for _, item := range demo.Plans {
data.Plans = append(data.Plans, &plan.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
})
}
for _, item := range demo.Certificates {
data.Certificates = append(
data.Certificates,
&certificate.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
Type: item.Type,
},
)
}
for _, item := range demo.Secrets {
data.Secrets = append(data.Secrets, &secret.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
Type: item.Type,
})
}
for _, item := range demo.Pods {
data.Pods = append(data.Pods, &pod.Completion{
Id: item.Id,
Name: item.Name,
Organization: item.Organization,
})
}
for _, item := range demo.Units {
data.Units = append(data.Units, &unit.Completion{
Id: item.Id,
Pod: item.Pod,
Organization: item.Organization,
Name: item.Name,
Kind: item.Kind,
})
}
c.JSON(200, data)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
var userOrg bson.ObjectID
orgIdStr := c.GetHeader("Organization")
if orgIdStr != "" {
orgId, ok := utils.ParseObjectId(orgIdStr)
if !ok {
utils.AbortWithStatus(c, 400)
return
}
org, err := organization.Get(db, orgId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
match := usr.RolesMatch(org.Roles)
if !match {
utils.AbortWithStatus(c, 401)
return
}
userOrg = org.Id
} else {
orgs, err := organization.GetAll(db, &bson.M{
"roles": &bson.M{
"$in": usr.Roles,
},
})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if len(orgs) > 0 {
org := orgs[0]
match := usr.RolesMatch(org.Roles)
if !match {
utils.AbortWithStatus(c, 401)
return
}
userOrg = org.Id
}
}
if userOrg.IsZero() {
utils.AbortWithStatus(c, 400)
return
}
cmpl, err := completion.GetCompletion(db, userOrg, usr.Roles)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, cmpl)
}
================================================
FILE: uhandlers/csrf.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/csrf"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/utils"
)
type csrfData struct {
Token string `json:"token"`
Theme string `json:"theme"`
EditorTheme string `json:"editor_theme"`
OracleLicense bool `json:"oracle_license"`
}
func csrfGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
token, err := csrf.NewToken(db, authr.SessionId())
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
oracleLicense := usr.OracleLicense
if demo.IsDemo() {
oracleLicense = true
}
data := &csrfData{
Token: token,
Theme: usr.Theme,
EditorTheme: usr.EditorTheme,
OracleLicense: oracleLicense,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/datacenter.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/utils"
)
func datacentersGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dcs, err := datacenter.GetAllNamesOrg(db, userOrg)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, dcs)
}
================================================
FILE: uhandlers/devices.go
================================================
package uhandlers
import (
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/device"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/secondary"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/validator"
)
type deviceData struct {
Name string `json:"name"`
}
func devicePut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &deviceData{}
devcId, ok := utils.ParseObjectId(c.Param("device_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devc, err := device.GetUser(db, devcId, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devc.Name = data.Name
fields := set.NewSet(
"name",
)
errData, err := devc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = devc.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "device.change")
c.JSON(200, devc)
}
func deviceDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devcId, ok := utils.ParseObjectId(c.Param("device_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
count, err := device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if count <= 1 {
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
err = device.RemoveUser(db, devcId, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
count, err = device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if count == 0 {
if !usr.Disabled {
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("disabled"))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserAccountDisable,
audit.Fields{
"reason": "All authentication devices removed",
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
errData := &errortypes.ErrorData{
Error: "device_empty",
Message: "Account disabled contact an administrator",
}
c.JSON(401, errData)
return
}
event.PublishDispatch(db, "device.change")
c.JSON(200, nil)
}
func devicesGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
devices, err := device.GetAllSorted(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, devices)
}
type devicesWanRegisterRespData struct {
Token string `json:"token"`
Options interface{} `json:"options"`
}
func deviceWanRegisterGet(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if node.Self.WebauthnDomain == "" {
errData := &errortypes.ErrorData{
Error: "webauthn_domain_unavailable",
Message: "WebAuthn domain must be configured",
}
c.JSON(400, errData)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_, secProviderId, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "add_device_register"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserAuthFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(400, errData)
return
}
deviceCount, err := device.CountSecondary(db, usr.Id)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if deviceCount > 0 || !secProviderId.IsZero() {
secType := ""
var secProvider bson.ObjectID
if deviceCount == 0 {
secType = secondary.UserManage
secProvider = secProviderId
} else {
secType = secondary.UserManageDevice
secProvider = secondary.DeviceProvider
}
secd, err := secondary.New(db, usr.Id, secType, secProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
}
secd, err := secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,
secondary.DeviceProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserDeviceRegisterRequest,
audit.Fields{},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
jsonResp, errData, err := secd.DeviceRegisterRequest(db,
utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
resp := &devicesWanRegisterRespData{
Token: secd.Id,
Options: jsonResp,
}
c.JSON(200, resp)
}
type devicesWanRegisterData struct {
Token string `json:"token"`
Name string `json:"name"`
}
func deviceWanRegisterPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &devicesWanRegisterData{}
body, err := utils.CopyBody(c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token,
secondary.UserManageDeviceRegister)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(400, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
devc, errData, err := secd.DeviceRegisterResponse(
db, utils.GetOrigin(c.Request), body, data.Name)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = audit.New(
db,
c.Request,
usr.Id,
audit.DeviceRegister,
audit.Fields{
"device_id": devc.Id,
},
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "device.change")
c.JSON(200, nil)
}
type deviceSecondaryData struct {
Type string `json:"type"`
Token string `json:"token"`
Factor string `json:"factor"`
Passcode string `json:"passcode"`
}
func deviceSecondaryPut(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &deviceSecondaryData{}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token, secondary.UserManage)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(400, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
errData, err := secd.Handle(db, c.Request, data.Factor, data.Passcode)
if err != nil {
if _, ok := err.(*secondary.IncompleteError); ok {
c.Status(206)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
if errData != nil {
c.JSON(400, errData)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secd, err = secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,
secondary.DeviceProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
jsonResp, errData, err := secd.DeviceRegisterRequest(db,
utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
resp := &devicesWanRegisterRespData{
Token: secd.Id,
Options: jsonResp,
}
c.JSON(200, resp)
}
func deviceWanRequestGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
token := c.Query("token")
secd, err := secondary.Get(db, token, secondary.UserManageDevice)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(400, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
resp, errData, err := secd.DeviceRequest(
db, utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
c.JSON(200, resp)
}
type deviceWanRespondData struct {
Token string `json:"token"`
}
func deviceWanRespondPost(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &deviceWanRespondData{}
body, err := utils.CopyBody(c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
secd, err := secondary.Get(db, data.Token, secondary.UserManageDevice)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "secondary_expired",
Message: "Secondary authentication has expired",
}
c.JSON(400, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
_, secProviderId, errAudit, errData, err := validator.ValidateUser(
db, usr, false, c.Request)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
errData, err = secd.DeviceRespond(
db, utils.GetOrigin(c.Request), body)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
if errAudit == nil {
errAudit = audit.Fields{
"error": errData.Error,
"message": errData.Message,
}
}
errAudit["method"] = "add_device_register"
err = audit.New(
db,
c.Request,
usr.Id,
audit.UserAuthFailed,
errAudit,
)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(400, errData)
return
}
if !secProviderId.IsZero() {
secd, err := secondary.New(db, usr.Id,
secondary.UserManage, secProviderId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data, err := secd.GetData()
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(201, data)
return
}
secd, err = secondary.New(db, usr.Id, secondary.UserManageDeviceRegister,
secondary.DeviceProvider)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
jsonResp, errData, err := secd.DeviceRegisterRequest(db,
utils.GetOrigin(c.Request))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
resp := &devicesWanRegisterRespData{
Token: secd.Id,
Options: jsonResp,
}
c.JSON(200, resp)
}
================================================
FILE: uhandlers/disk.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/aggregate"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/storage"
"github.com/pritunl/pritunl-cloud/utils"
)
type diskData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Instance bson.ObjectID `json:"instance"`
Index string `json:"index"`
Type string `json:"type"`
Node bson.ObjectID `json:"node"`
Pool bson.ObjectID `json:"pool"`
DeleteProtection bool `json:"delete_protection"`
FileSystem string `json:"file_system"`
Image bson.ObjectID `json:"image"`
RestoreImage bson.ObjectID `json:"restore_image"`
Backing bool `json:"backing"`
Action string `json:"action"`
Size int `json:"size"`
LvSize int `json:"lv_size"`
NewSize int `json:"new_size"`
Backup bool `json:"backup"`
}
type disksMultiData struct {
Ids []bson.ObjectID `json:"ids"`
Action string `json:"action"`
}
type disksData struct {
Disks []*aggregate.DiskAggregate `json:"disks"`
Count int64 `json:"count"`
}
func diskPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &diskData{}
diskId, ok := utils.ParseObjectId(c.Param("disk_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
dsk, err := disk.GetOrg(db, userOrg, diskId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
fields := set.NewSet(
"name",
"comment",
"type",
"instance",
"delete_protection",
"index",
"backup",
"new_size",
)
if !dta.Instance.IsZero() {
exists, err := instance.ExistsOrg(db, userOrg, dta.Instance)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
}
dsk.PreCommit()
if dta.Action != "" && dsk.Action != "" {
errData := &errortypes.ErrorData{
Error: "disk_actin_active",
Message: "Disk action already active",
}
c.JSON(400, errData)
return
}
if dsk.IsActive() && dta.Action == disk.Snapshot {
dsk.Action = disk.Snapshot
fields.Add("action")
} else if dsk.IsActive() && dta.Action == disk.Backup {
dsk.Action = disk.Backup
fields.Add("action")
} else if dsk.IsActive() && dta.Action == disk.Expand {
dsk.Action = disk.Expand
dsk.NewSize = dta.NewSize
fields.Add("action")
} else if dsk.IsActive() && dta.Action == disk.Restore {
img, err := image.GetOrg(db, userOrg, dta.RestoreImage)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if img.Disk != dsk.Id {
errData := &errortypes.ErrorData{
Error: "invalid_restore_image",
Message: "Invalid restore image",
}
c.JSON(400, errData)
return
}
dsk.Action = disk.Restore
dsk.RestoreImage = img.Id
fields.Add("action")
fields.Add("restore_image")
}
errData, err := dsk.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = dsk.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "disk.change")
c.JSON(200, dsk)
}
func diskPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &diskData{
Name: "new-disk",
}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
if !dta.Instance.IsZero() {
exists, err := instance.ExistsOrg(db, userOrg, dta.Instance)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
}
nde, err := node.Get(db, dta.Node)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := datacenter.ExistsOrg(db, userOrg, nde.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
imgSystemType := ""
imgSystemKind := ""
if !dta.Image.IsZero() {
img, err := image.GetOrgPublic(db, userOrg, dta.Image)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
imgSystemType = img.GetSystemType()
imgSystemKind = img.GetSystemKind()
store, err := storage.Get(db, img.Storage)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
available, err := data.ImageAvailable(store, img)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !available {
if store.IsOracle() {
errData := &errortypes.ErrorData{
Error: "image_not_available",
Message: "Image not restored from archive",
}
c.JSON(400, errData)
} else {
errData := &errortypes.ErrorData{
Error: "image_not_available",
Message: "Image not restored from glacier",
}
c.JSON(400, errData)
}
return
}
}
dsk := &disk.Disk{
Name: dta.Name,
Comment: dta.Comment,
Organization: userOrg,
Instance: dta.Instance,
Datacenter: nde.Datacenter,
Zone: nde.Zone,
Index: dta.Index,
Type: dta.Type,
SystemType: imgSystemType,
SystemKind: imgSystemKind,
Node: dta.Node,
Pool: dta.Pool,
Image: dta.Image,
DeleteProtection: dta.DeleteProtection,
FileSystem: dta.FileSystem,
Backing: dta.Backing,
Size: dta.Size,
LvSize: dta.LvSize,
Backup: dta.Backup,
}
errData, err := dsk.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = dsk.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "disk.change")
c.JSON(200, dsk)
}
func disksPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &disksMultiData{}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
if data.Action != disk.Snapshot && data.Action != disk.Backup {
errData := &errortypes.ErrorData{
Error: "invalid_action",
Message: "Invalid disk action",
}
c.JSON(400, errData)
return
}
doc := bson.M{
"action": data.Action,
}
err = disk.UpdateMultiOrg(db, userOrg, data.Ids, &doc)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "disk.change")
c.JSON(200, nil)
}
func diskDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
diskId, ok := utils.ParseObjectId(c.Param("disk_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
dsk, err := disk.Get(db, diskId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if dsk.DeleteProtection {
errData := &errortypes.ErrorData{
Error: "delete_protection",
Message: "Cannot delete disk with delete protection",
}
c.JSON(400, errData)
return
}
if !dsk.Instance.IsZero() {
inst, e := instance.Get(db, dsk.Instance)
if e != nil {
err = e
return
}
if inst.DeleteProtection {
errData := &errortypes.ErrorData{
Error: "instance_delete_protection",
Message: "Cannot delete disk attached to " +
"instance with delete protection",
}
c.JSON(400, errData)
return
}
}
err = disk.DeleteOrg(db, userOrg, diskId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "disk.change")
c.JSON(200, nil)
}
func disksDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := []bson.ObjectID{}
err := c.Bind(&dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
err = disk.DeleteMultiOrg(db, userOrg, dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "disk.change")
c.JSON(200, nil)
}
func diskGet(c *gin.Context) {
if demo.IsDemo() {
dsk := demo.Disks[0]
c.JSON(200, dsk)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
diskId, ok := utils.ParseObjectId(c.Param("disk_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
dsk, err := disk.GetOrg(db, userOrg, diskId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, dsk)
}
func disksGet(c *gin.Context) {
if demo.IsDemo() {
data := &disksData{
Disks: demo.Disks,
Count: int64(len(demo.Disks)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
diskId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = diskId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
inst, ok := utils.ParseObjectId(c.Query("instance"))
if ok {
query["instance"] = inst
}
nodeId, ok := utils.ParseObjectId(c.Query("node"))
if ok {
query["node"] = nodeId
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
disks, count, err := aggregate.GetDiskPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
dta := &disksData{
Disks: disks,
Count: count,
}
c.JSON(200, dta)
}
================================================
FILE: uhandlers/domain.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/aggregate"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/domain"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
)
type domainData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Type string `json:"type"`
Secret bson.ObjectID `json:"secret"`
RootDomain string `json:"root_domain"`
Records []*domain.Record `json:"records"`
}
type domainsData struct {
Domains []*domain.Domain `json:"domains"`
Count int64 `json:"count"`
}
func domainPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &domainData{}
domainId, ok := utils.ParseObjectId(c.Param("domain_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
domn, err := domain.GetOrg(db, userOrg, domainId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = domn.LoadRecords(db, true)
if err != nil {
return
}
domn.PreCommit()
domn.Name = data.Name
domn.Comment = data.Comment
domn.Type = data.Type
domn.Secret = data.Secret
domn.RootDomain = data.RootDomain
domn.Records = data.Records
fields := set.NewSet(
"name",
"comment",
"type",
"secret",
"root_domain",
)
errData, err := domn.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = domn.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = domn.CommitRecords(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "domain.change")
c.JSON(200, domn)
}
func domainPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &domainData{
Name: "new.domain",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
domn := &domain.Domain{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
Type: data.Type,
Secret: data.Secret,
RootDomain: data.RootDomain,
}
errData, err := domn.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = domn.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "domain.change")
c.JSON(200, domn)
}
func domainDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
domainId, ok := utils.ParseObjectId(c.Param("domain_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := domain.RemoveOrg(db, userOrg, domainId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "domain.change")
c.JSON(200, nil)
}
func domainsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = domain.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "domain.change")
c.JSON(200, nil)
}
func domainGet(c *gin.Context) {
if demo.IsDemo() {
domn := demo.Domains[0]
c.JSON(200, domn)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
domainId, ok := utils.ParseObjectId(c.Param("domain_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
domn, err := domain.GetOrg(db, userOrg, domainId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = domn.LoadRecords(db, true)
if err != nil {
return
}
domn.Json()
c.JSON(200, domn)
}
func domainsGet(c *gin.Context) {
if demo.IsDemo() {
data := &domainsData{
Domains: demo.Domains,
Count: int64(len(demo.Domains)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
if c.Query("names") == "true" {
domns, err := domain.GetAllName(db, &bson.M{
"organization": userOrg,
})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, domns)
} else {
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
domainId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = domainId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
domains, count, err := aggregate.GetDomainPaged(
db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &domainsData{
Domains: domains,
Count: count,
}
c.JSON(200, data)
}
}
================================================
FILE: uhandlers/event.go
================================================
package uhandlers
import (
"context"
"fmt"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
)
const (
writeTimeout = 10 * time.Second
pingInterval = 30 * time.Second
pingWait = 40 * time.Second
)
func eventGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
socket := &event.WebSocket{}
defer func() {
socket.Close()
event.WebSocketsLock.Lock()
event.WebSockets.Remove(socket)
event.WebSocketsLock.Unlock()
}()
event.WebSocketsLock.Lock()
event.WebSockets.Add(socket)
event.WebSocketsLock.Unlock()
ctx, cancel := context.WithCancel(db)
socket.Cancel = cancel
conn, err := event.Upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "uhandlers: Failed to upgrade request"),
}
utils.AbortWithError(c, 500, err)
return
}
socket.Conn = conn
err = conn.SetReadDeadline(time.Now().Add(pingWait))
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "uhandlers: Failed to set read deadline"),
}
utils.AbortWithError(c, 500, err)
return
}
conn.SetPongHandler(func(x string) (err error) {
err = conn.SetReadDeadline(time.Now().Add(pingWait))
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "uhandlers: Failed to set read deadline"),
}
utils.AbortWithError(c, 500, err)
return
}
return
})
lst, err := event.SubscribeListener(db, []string{"dispatch"})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
socket.Listener = lst
ticker := time.NewTicker(pingInterval)
socket.Ticker = ticker
sub := lst.Listen()
defer lst.Close()
go func() {
defer func() {
r := recover()
if r != nil && !socket.Closed {
logrus.WithFields(logrus.Fields{
"error": errors.New(fmt.Sprintf("%s", r)),
}).Error("mhandlers: Event panic")
}
}()
for {
_, _, err := conn.NextReader()
if err != nil {
conn.Close()
return
}
}
}()
for {
select {
case <-ctx.Done():
return
case msg, ok := <-sub:
if !ok {
err = conn.WriteControl(websocket.CloseMessage, []byte{},
time.Now().Add(writeTimeout))
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err,
"uhandlers: Failed to set write control"),
}
return
}
return
}
err = conn.SetWriteDeadline(time.Now().Add(writeTimeout))
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err,
"uhandlers: Failed to set write deadline"),
}
return
}
err = conn.WriteJSON(msg)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err,
"uhandlers: Failed to set write json"),
}
return
}
case <-ticker.C:
err = conn.WriteControl(websocket.PingMessage, []byte{},
time.Now().Add(writeTimeout))
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err,
"uhandlers: Failed to set write control"),
}
return
}
}
}
}
================================================
FILE: uhandlers/firewall.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/firewall"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/utils"
)
type firewallData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Roles []string `json:"roles"`
Ingress []*firewall.Rule `json:"ingress"`
}
type firewallsData struct {
Firewalls []*firewall.Firewall `json:"firewalls"`
Count int64 `json:"count"`
}
func firewallPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &firewallData{}
firewallId, ok := utils.ParseObjectId(c.Param("firewall_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
fire, err := firewall.GetOrg(db, userOrg, firewallId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
fire.Name = data.Name
fire.Comment = data.Comment
fire.Roles = data.Roles
fire.Ingress = data.Ingress
fields := set.NewSet(
"name",
"comment",
"roles",
"ingress",
)
errData, err := fire.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = fire.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "firewall.change")
c.JSON(200, fire)
}
func firewallPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &firewallData{
Name: "new-firewall",
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
fire := &firewall.Firewall{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
Roles: data.Roles,
Ingress: data.Ingress,
}
errData, err := fire.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = fire.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "firewall.change")
c.JSON(200, fire)
}
func firewallDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
firewallId, ok := utils.ParseObjectId(c.Param("firewall_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
errData, err := relations.CanDeleteOrg(db, "firewall", userOrg, firewallId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = firewall.RemoveOrg(db, userOrg, firewallId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "firewall.change")
c.JSON(200, nil)
}
func firewallsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
errData, err := relations.CanDeleteOrgAll(db, "firewall", userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = firewall.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "firewall.change")
c.JSON(200, nil)
}
func firewallGet(c *gin.Context) {
if demo.IsDemo() {
fire := demo.Firewalls[0]
c.JSON(200, fire)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
firewallId, ok := utils.ParseObjectId(c.Param("firewall_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
fire, err := firewall.GetOrg(db, userOrg, firewallId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, fire)
}
func firewallsGet(c *gin.Context) {
if demo.IsDemo() {
data := &firewallsData{
Firewalls: demo.Firewalls,
Count: int64(len(demo.Firewalls)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
firewallId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = firewallId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
role := strings.TrimSpace(c.Query("role"))
if role != "" {
if strings.HasPrefix(role, "~") {
role := role[1:]
if strings.HasPrefix(role, "!") {
query["roles"] = &bson.M{
"$not": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role[1:])),
"$options": "i",
},
}
} else {
query["$or"] = []*bson.M{
&bson.M{
"roles": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role)),
"$options": "i",
},
},
}
}
} else {
if strings.HasPrefix(role, "!") {
role = strings.TrimLeft(role, "!")
query["roles"] = &bson.M{
"$ne": role,
}
} else {
query["roles"] = role
}
}
}
firewalls, count, err := firewall.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &firewallsData{
Firewalls: firewalls,
Count: count,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/handlers.go
================================================
package uhandlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/config"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/middlewear"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/static"
)
var (
store *static.Store
fileServer http.Handler
)
func Register(engine *gin.Engine) {
engine.Use(middlewear.Limiter)
engine.Use(middlewear.Counter)
engine.Use(middlewear.Recovery)
engine.Use(middlewear.Headers)
dbGroup := engine.Group("")
dbGroup.Use(middlewear.Database)
sessGroup := dbGroup.Group("")
sessGroup.Use(middlewear.SessionUser)
authGroup := sessGroup.Group("")
authGroup.Use(middlewear.AuthUser)
csrfGroup := authGroup.Group("")
csrfGroup.Use(middlewear.CsrfToken)
orgGroup := csrfGroup.Group("")
orgGroup.Use(middlewear.UserOrg)
engine.NoRoute(middlewear.NotFound)
orgGroup.GET("/alert", alertsGet)
orgGroup.GET("/alert/:alert_id", alertGet)
orgGroup.PUT("/alert/:alert_id", alertPut)
orgGroup.POST("/alert", alertPost)
orgGroup.DELETE("/alert", alertsDelete)
orgGroup.DELETE("/alert/:alert_id", alertDelete)
engine.GET("/auth/state", authStateGet)
dbGroup.POST("/auth/session", authSessionPost)
dbGroup.POST("/auth/secondary", authSecondaryPost)
dbGroup.GET("/auth/request", authRequestGet)
dbGroup.GET("/auth/callback", authCallbackGet)
engine.GET("/auth/u2f/app.json", authU2fAppGet)
dbGroup.GET("/auth/webauthn/request", authWanRequestGet)
dbGroup.POST("/auth/webauthn/respond", authWanRespondPost)
dbGroup.GET("/auth/webauthn/register", authWanRegisterGet)
dbGroup.POST("/auth/webauthn/register", authWanRegisterPost)
sessGroup.GET("/logout", logoutGet)
sessGroup.GET("/logout_all", logoutAllGet)
orgGroup.GET("/authority", authoritiesGet)
orgGroup.GET("/authority/:authority_id", authorityGet)
orgGroup.PUT("/authority/:authority_id", authorityPut)
orgGroup.POST("/authority", authorityPost)
orgGroup.DELETE("/authority", authoritiesDelete)
orgGroup.DELETE("/authority/:authority_id", authorityDelete)
orgGroup.GET("/balancer", balancersGet)
orgGroup.GET("/balancer/:balancer_id", balancerGet)
orgGroup.PUT("/balancer/:balancer_id", balancerPut)
orgGroup.POST("/balancer", balancerPost)
orgGroup.DELETE("/balancer", balancersDelete)
orgGroup.DELETE("/balancer/:balancer_id", balancerDelete)
orgGroup.GET("/certificate", certificatesGet)
orgGroup.GET("/certificate/:cert_id", certificateGet)
orgGroup.PUT("/certificate/:cert_id", certificatePut)
orgGroup.POST("/certificate", certificatePost)
orgGroup.DELETE("/certificate/", certificatesDelete)
orgGroup.DELETE("/certificate/:cert_id", certificateDelete)
engine.GET("/check", checkGet)
authGroup.GET("/csrf", csrfGet)
csrfGroup.GET("/completion", completionGet)
orgGroup.GET("/datacenter", datacentersGet)
csrfGroup.GET("/device", devicesGet)
csrfGroup.PUT("/device/:device_id", devicePut)
csrfGroup.DELETE("/device/:device_id", deviceDelete)
csrfGroup.PUT("/device/:device_id/secondary", deviceSecondaryPut)
csrfGroup.GET("/device/:device_id/request", deviceWanRequestGet)
csrfGroup.POST("/device/:device_id/respond", deviceWanRespondPost)
csrfGroup.GET("/device/:device_id/register", deviceWanRegisterGet)
csrfGroup.POST("/device/:device_id/register", deviceWanRegisterPost)
orgGroup.GET("/domain", domainsGet)
orgGroup.GET("/domain/:domain_id", domainGet)
orgGroup.PUT("/domain/:domain_id", domainPut)
orgGroup.POST("/domain", domainPost)
orgGroup.DELETE("/domain", domainsDelete)
orgGroup.DELETE("/domain/:domain_id", domainDelete)
orgGroup.GET("/disk", disksGet)
orgGroup.GET("/disk/:disk_id", diskGet)
orgGroup.PUT("/disk", disksPut)
orgGroup.PUT("/disk/:disk_id", diskPut)
orgGroup.POST("/disk", diskPost)
orgGroup.DELETE("/disk", disksDelete)
orgGroup.DELETE("/disk/:disk_id", diskDelete)
csrfGroup.GET("/event", eventGet)
orgGroup.GET("/firewall", firewallsGet)
orgGroup.GET("/firewall/:firewall_id", firewallGet)
orgGroup.PUT("/firewall/:firewall_id", firewallPut)
orgGroup.POST("/firewall", firewallPost)
orgGroup.DELETE("/firewall", firewallsDelete)
orgGroup.DELETE("/firewall/:firewall_id", firewallDelete)
orgGroup.GET("/image", imagesGet)
orgGroup.GET("/image/:image_id", imageGet)
orgGroup.PUT("/image/:image_id", imagePut)
orgGroup.DELETE("/image", imagesDelete)
orgGroup.DELETE("/image/:image_id", imageDelete)
orgGroup.GET("/instance", instancesGet)
orgGroup.PUT("/instance", instancesPut)
orgGroup.GET("/instance/:instance_id", instanceGet)
orgGroup.GET("/instance/:instance_id/vnc", instanceVncGet)
orgGroup.PUT("/instance/:instance_id", instancePut)
orgGroup.POST("/instance", instancePost)
orgGroup.DELETE("/instance", instancesDelete)
orgGroup.DELETE("/instance/:instance_id", instanceDelete)
csrfGroup.PUT("/license", licensePut)
orgGroup.GET("/node", nodesGet)
orgGroup.GET("/plan", plansGet)
orgGroup.GET("/plan/:plan_id", planGet)
orgGroup.PUT("/plan/:plan_id", planPut)
orgGroup.POST("/plan", planPost)
orgGroup.DELETE("/plan", plansDelete)
orgGroup.DELETE("/plan/:plan_id", planDelete)
csrfGroup.GET("/pool", poolsGet)
orgGroup.GET("/relations/:kind/:id", relationsGet)
orgGroup.GET("/secret", secretsGet)
orgGroup.GET("/secret/:secr_id", secretGet)
orgGroup.PUT("/secret/:secr_id", secretPut)
orgGroup.POST("/secret", secretPost)
orgGroup.DELETE("/secret", secretsDelete)
orgGroup.DELETE("/secret/:secr_id", secretDelete)
orgGroup.GET("/pod", podsGet)
orgGroup.GET("/pod/:pod_id", podGet)
orgGroup.PUT("/pod/:pod_id", podPut)
orgGroup.PUT("/pod/:pod_id/drafts", podDraftsPut)
orgGroup.PUT("/pod/:pod_id/deploy", podDeployPut)
orgGroup.POST("/pod", podPost)
orgGroup.DELETE("/pod", podsDelete)
orgGroup.DELETE("/pod/:pod_id", podDelete)
orgGroup.GET("/pod/:pod_id/unit/:unit_id", podUnitGet)
orgGroup.PUT("/pod/:pod_id/unit/:unit_id/deployment",
podUnitDeploymentsPut)
orgGroup.PUT("/pod/:pod_id/unit/:unit_id/deployment/:deployment_id",
podUnitDeploymentPut)
orgGroup.POST("/pod/:pod_id/unit/:unit_id/deployment",
podUnitDeploymentPost)
orgGroup.GET(
"/pod/:pod_id/unit/:unit_id/deployment/:deployment_id/log",
podUnitDeploymentLogGet,
)
orgGroup.GET("/pod/:pod_id/unit/:unit_id/spec", podUnitSpecsGet)
orgGroup.GET("/pod/:pod_id/unit/:unit_id/spec/:spec_id", podUnitSpecGet)
csrfGroup.GET("/shape", shapesGet)
csrfGroup.GET("/organization", organizationsGet)
csrfGroup.PUT("/theme", themePut)
orgGroup.GET("/vpc", vpcsGet)
orgGroup.GET("/vpc/:vpc_id", vpcGet)
orgGroup.PUT("/vpc/:vpc_id", vpcPut)
orgGroup.GET("/vpc/:vpc_id/routes", vpcRoutesGet)
orgGroup.PUT("/vpc/:vpc_id/routes", vpcRoutesPut)
orgGroup.POST("/vpc", vpcPost)
orgGroup.DELETE("/vpc", vpcsDelete)
orgGroup.DELETE("/vpc/:vpc_id", vpcDelete)
orgGroup.GET("/zone", zonesGet)
engine.GET("/robots.txt", middlewear.RobotsGet)
if constants.Production {
sessGroup.GET("/", staticIndexGet)
engine.GET("/login", staticLoginGet)
engine.GET("/logo.png", staticLogoGet)
authGroup.GET("/static/*path", staticGet)
} else {
fs := gin.Dir(config.StaticTestingRoot, false)
fileServer = http.FileServer(fs)
sessGroup.GET("/", staticTestingGet)
engine.GET("/login", staticTestingGet)
engine.GET("/logo.png", staticTestingGet)
authGroup.GET("/static/*path", staticTestingGet)
}
}
func init() {
module := requires.New("uhandlers")
module.After("settings")
module.Handler = func() (err error) {
if constants.Production {
store, err = static.NewStore(config.StaticRoot)
if err != nil {
return
}
}
return
}
}
================================================
FILE: uhandlers/image.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/utils"
)
type imageData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
}
type imagesData struct {
Images []*image.Image `json:"images"`
Count int64 `json:"count"`
}
func imagePut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &imageData{}
imageId, ok := utils.ParseObjectId(c.Param("image_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
img, err := image.GetOrg(db, userOrg, imageId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
img.Name = dta.Name
img.Comment = dta.Comment
fields := set.NewSet(
"name",
"comment",
)
errData, err := img.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = img.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "image.change")
c.JSON(200, img)
}
func imageDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
imageId, ok := utils.ParseObjectId(c.Param("image_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := data.DeleteImageOrg(db, userOrg, imageId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "image.change")
event.PublishDispatch(db, "pod.change")
c.JSON(200, nil)
}
func imagesDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := []bson.ObjectID{}
err := c.Bind(&dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = data.DeleteImagesOrg(db, userOrg, dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "image.change")
event.PublishDispatch(db, "pod.change")
c.JSON(200, nil)
}
func imageGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
imageId, ok := utils.ParseObjectId(c.Param("image_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
img, err := image.GetOrgPublic(db, userOrg, imageId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
img.Json()
c.JSON(200, img)
}
func imagesGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dcId, _ := utils.ParseObjectId(c.Query("datacenter"))
if !dcId.IsZero() {
dc, err := datacenter.Get(db, dcId)
if err != nil {
return
}
storages := dc.PublicStorages
if storages == nil {
storages = []bson.ObjectID{}
}
if len(storages) == 0 {
c.JSON(200, []bson.ObjectID{})
return
}
query := &bson.M{
"organization": image.Global,
"storage": &bson.M{
"$in": dc.PublicStorages,
},
}
if demo.IsDemo() {
query = &bson.M{
"organization": image.Global,
}
}
images, err := image.GetAllNames(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, img := range images {
img.Json()
}
if !dc.PrivateStorage.IsZero() {
query = &bson.M{
"organization": userOrg,
"storage": dc.PrivateStorage,
}
images2, err := image.GetAllNames(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, img := range images2 {
img.Json()
images = append(images, img)
}
}
c.JSON(200, images)
} else {
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": &bson.M{
"$in": []bson.ObjectID{
image.Global,
userOrg,
},
},
}
imageId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = imageId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query = bson.M{
"$and": []*bson.M{
&bson.M{
"organization": &bson.M{
"$in": []bson.ObjectID{
image.Global,
userOrg,
},
},
},
&bson.M{
"$or": []*bson.M{
&bson.M{
"name": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(name)),
"$options": "i",
},
},
&bson.M{
"key": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(name)),
"$options": "i",
},
},
},
},
},
}
}
typ := strings.TrimSpace(c.Query("type"))
if typ != "" {
query["type"] = typ
}
images, count, err := image.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, img := range images {
img.Json()
}
dta := &imagesData{
Images: images,
Count: count,
}
c.JSON(200, dta)
}
}
================================================
FILE: uhandlers/instance.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/data"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/drive"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/image"
"github.com/pritunl/pritunl-cloud/instance"
"github.com/pritunl/pritunl-cloud/iscsi"
"github.com/pritunl/pritunl-cloud/iso"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/nodeport"
"github.com/pritunl/pritunl-cloud/pci"
"github.com/pritunl/pritunl-cloud/storage"
"github.com/pritunl/pritunl-cloud/usb"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/pritunl/pritunl-cloud/vpc"
"github.com/pritunl/pritunl-cloud/zone"
)
type instanceData struct {
Id bson.ObjectID `json:"id"`
Zone bson.ObjectID `json:"zone"`
Vpc bson.ObjectID `json:"vpc"`
Subnet bson.ObjectID `json:"subnet"`
CloudSubnet string `json:"cloud_subnet"`
Shape bson.ObjectID `json:"shape"`
Node bson.ObjectID `json:"node"`
DiskType string `json:"disk_type"`
DiskPool bson.ObjectID `json:"disk_pool"`
Image bson.ObjectID `json:"image"`
ImageBacking bool `json:"image_backing"`
Name string `json:"name"`
Comment string `json:"comment"`
Action string `json:"action"`
RootEnabled bool `json:"root_enabled"`
Uefi bool `json:"uefi"`
SecureBoot bool `json:"secure_boot"`
Tpm bool `json:"tpm"`
DhcpServer bool `json:"dhcp_server"`
CloudType string `json:"cloud_type"`
CloudScript string `json:"cloud_script"`
DeleteProtection bool `json:"delete_protection"`
SkipSourceDestCheck bool `json:"skip_source_dest_check"`
InitDiskSize int `json:"init_disk_size"`
Memory int `json:"memory"`
Processors int `json:"processors"`
Roles []string `json:"roles"`
Isos []*iso.Iso `json:"isos"`
UsbDevices []*usb.Device `json:"usb_devices"`
PciDevices []*pci.Device `json:"pci_devices"`
DriveDevices []*drive.Device `json:"drive_devices"`
IscsiDevices []*iscsi.Device `json:"iscsi_devices"`
Mounts []*instance.Mount `json:"mounts"`
Vnc bool `json:"vnc"`
Spice bool `json:"spice"`
Gui bool `json:"gui"`
NodePorts []*nodeport.Mapping `json:"node_ports"`
NoPublicAddress bool `json:"no_public_address"`
NoPublicAddress6 bool `json:"no_public_address6"`
NoHostAddress bool `json:"no_host_address"`
Count int `json:"count"`
}
type instanceMultiData struct {
Ids []bson.ObjectID `json:"ids"`
Action string `json:"action"`
}
type instancesData struct {
Instances []*instance.Instance `json:"instances"`
Count int64 `json:"count"`
}
func instancePut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &instanceData{}
instanceId, ok := utils.ParseObjectId(c.Param("instance_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
inst, err := instance.GetOrg(db, userOrg, instanceId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := vpc.ExistsOrg(db, userOrg, dta.Vpc)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
inst.PreCommit()
inst.Name = dta.Name
inst.Comment = dta.Comment
inst.Vpc = dta.Vpc
inst.Subnet = dta.Subnet
inst.CloudSubnet = dta.CloudSubnet
if dta.Action != "" {
inst.Action = dta.Action
}
inst.Uefi = dta.Uefi
inst.SecureBoot = dta.SecureBoot
inst.Tpm = dta.Tpm
inst.DhcpServer = dta.DhcpServer
inst.CloudType = dta.CloudType
inst.CloudScript = dta.CloudScript
inst.DeleteProtection = dta.DeleteProtection
inst.SkipSourceDestCheck = dta.SkipSourceDestCheck
inst.Memory = dta.Memory
inst.Processors = dta.Processors
inst.Roles = dta.Roles
inst.Isos = dta.Isos
inst.UsbDevices = dta.UsbDevices
inst.PciDevices = dta.PciDevices
inst.DriveDevices = dta.DriveDevices
inst.IscsiDevices = dta.IscsiDevices
inst.Mounts = dta.Mounts
inst.RootEnabled = dta.RootEnabled
inst.Vnc = dta.Vnc
inst.Spice = dta.Spice
inst.Gui = dta.Gui
inst.NodePorts = dta.NodePorts
inst.NoPublicAddress = dta.NoPublicAddress
inst.NoPublicAddress6 = dta.NoPublicAddress6
inst.NoHostAddress = dta.NoHostAddress
fields := set.NewSet(
"unix_id",
"name",
"comment",
"datacenter",
"vpc",
"subnet",
"dhcp_ip",
"dhcp_ip6",
"cloud_subnet",
"state",
"restart",
"restart_block_ip",
"uefi",
"secure_boot",
"tpm",
"tpm_secret",
"dhcp_server",
"cloud_type",
"cloud_script",
"delete_protection",
"skip_source_dest_check",
"memory",
"processors",
"roles",
"isos",
"usb_devices",
"pci_devices",
"drive_devices",
"iscsi_devices",
"mounts",
"root_enabled",
"root_passwd",
"vnc",
"vnc_display",
"vnc_password",
"spice",
"spice_port",
"spice_password",
"gui",
"node_ports",
"no_public_address",
"no_public_address6",
"no_host_address",
)
errData, err := inst.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
dskChange, err := inst.PostCommit(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = inst.CommitFields(db, fields)
if err != nil {
_ = inst.Cleanup(db)
utils.AbortWithError(c, 500, err)
return
}
err = inst.Cleanup(db)
if err != nil {
return
}
event.PublishDispatch(db, "instance.change")
if dskChange {
event.PublishDispatch(db, "disk.change")
}
c.JSON(200, inst)
}
func instancePost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &instanceData{
Name: "new-instance",
}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
zne, err := zone.Get(db, dta.Zone)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := datacenter.ExistsOrg(db, userOrg, zne.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
if !dta.Shape.IsZero() {
dta.Node = bson.NilObjectID
dta.DiskType = ""
dta.DiskPool = bson.NilObjectID
} else {
nde, err := node.Get(db, dta.Node)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if nde.Zone != zne.Id {
utils.AbortWithStatus(c, 405)
return
}
if dta.DiskType == disk.Lvm {
poolMatch := false
for _, plId := range nde.Pools {
if plId == dta.DiskPool {
poolMatch = true
}
}
if !poolMatch {
errData := &errortypes.ErrorData{
Error: "pool_not_found",
Message: "Pool not found",
}
c.JSON(400, errData)
return
}
}
}
exists, err = vpc.ExistsOrg(db, userOrg, dta.Vpc)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
if !dta.Image.IsZero() {
img, err := image.GetOrgPublic(db, userOrg, dta.Image)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
errData := &errortypes.ErrorData{
Error: "image_not_found",
Message: "Image not found",
}
c.JSON(400, errData)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
stre, err := storage.Get(db, img.Storage)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
available, err := data.ImageAvailable(stre, img)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !available {
if stre.IsOracle() {
errData := &errortypes.ErrorData{
Error: "image_not_available",
Message: "Image not restored from archive",
}
c.JSON(400, errData)
} else {
errData := &errortypes.ErrorData{
Error: "image_not_available",
Message: "Image not restored from glacier",
}
c.JSON(400, errData)
}
return
}
}
insts := []*instance.Instance{}
if dta.Count == 0 {
dta.Count = 1
}
for i := 0; i < dta.Count; i++ {
name := ""
if strings.Contains(dta.Name, "%") {
name = fmt.Sprintf(dta.Name, i+1)
} else {
name = dta.Name
}
inst := &instance.Instance{
Action: dta.Action,
Organization: userOrg,
Zone: dta.Zone,
Vpc: dta.Vpc,
Subnet: dta.Subnet,
CloudSubnet: dta.CloudSubnet,
Shape: dta.Shape,
Node: dta.Node,
DiskType: dta.DiskType,
DiskPool: dta.DiskPool,
Image: dta.Image,
ImageBacking: dta.ImageBacking,
Uefi: dta.Uefi,
SecureBoot: dta.SecureBoot,
Tpm: dta.Tpm,
DhcpServer: dta.DhcpServer,
CloudType: dta.CloudType,
CloudScript: dta.CloudScript,
DeleteProtection: dta.DeleteProtection,
SkipSourceDestCheck: dta.SkipSourceDestCheck,
Name: name,
Comment: dta.Comment,
InitDiskSize: dta.InitDiskSize,
Memory: dta.Memory,
Processors: dta.Processors,
Roles: dta.Roles,
Isos: dta.Isos,
UsbDevices: dta.UsbDevices,
PciDevices: dta.PciDevices,
DriveDevices: dta.DriveDevices,
IscsiDevices: dta.IscsiDevices,
Mounts: dta.Mounts,
RootEnabled: dta.RootEnabled,
Vnc: dta.Vnc,
Spice: dta.Spice,
Gui: dta.Gui,
NodePorts: dta.NodePorts,
NoPublicAddress: dta.NoPublicAddress,
NoHostAddress: dta.NoHostAddress,
}
errData, err := inst.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = inst.SyncNodePorts(db)
if err != nil {
return
}
err = inst.Insert(db)
if err != nil {
_ = inst.Cleanup(db)
utils.AbortWithError(c, 500, err)
return
}
err = inst.Cleanup(db)
if err != nil {
return
}
insts = append(insts, inst)
}
event.PublishDispatch(db, "instance.change")
if len(insts) == 1 {
c.JSON(200, insts[0])
} else {
c.JSON(200, insts)
}
}
func instancesPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := &instanceMultiData{}
err := c.Bind(dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
if !instance.ValidActions.Contains(dta.Action) {
errData := &errortypes.ErrorData{
Error: "invalid_action",
Message: "Invalid instance action",
}
c.JSON(400, errData)
return
}
doc := bson.M{
"action": dta.Action,
}
if dta.Action != instance.Start {
doc["restart"] = false
doc["restart_block_ip"] = false
}
err = instance.UpdateMultiOrg(db, userOrg, dta.Ids, &doc)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "instance.change")
c.JSON(200, nil)
}
func instanceDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
instanceId, ok := utils.ParseObjectId(c.Param("instance_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
inst, err := instance.Get(db, instanceId)
if err != nil {
return
}
if inst.DeleteProtection {
errData := &errortypes.ErrorData{
Error: "delete_protection",
Message: "Cannot delete instance with delete protection",
}
c.JSON(400, errData)
return
}
err = instance.DeleteOrg(db, userOrg, instanceId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "instance.change")
c.JSON(200, nil)
}
func instancesDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dta := []bson.ObjectID{}
err := c.Bind(&dta)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
err = instance.DeleteMultiOrg(db, userOrg, dta)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "instance.change")
c.JSON(200, nil)
}
func instanceGet(c *gin.Context) {
if demo.IsDemo() {
inst := demo.Instances[0]
inst.Guest.Timestamp = time.Now()
inst.Guest.Heartbeat = time.Now()
c.JSON(200, inst)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
instanceId, ok := utils.ParseObjectId(c.Param("instance_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
inst, err := instance.GetOrg(db, userOrg, instanceId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if demo.IsDemo() {
inst.State = vm.Running
inst.Action = instance.Start
inst.State = vm.Running
inst.Status = "Running"
inst.PublicIps = []string{
demo.RandIp(inst.Id),
}
inst.PublicIps6 = []string{
demo.RandIp6(inst.Id),
}
inst.PrivateIps = []string{
demo.RandPrivateIp(inst.Id),
}
inst.PrivateIps6 = []string{
demo.RandPrivateIp6(inst.Id),
}
inst.NetworkNamespace = vm.GetNamespace(inst.Id, 0)
}
c.JSON(200, inst)
}
func instancesGet(c *gin.Context) {
if demo.IsDemo() {
for _, inst := range demo.Instances {
inst.Guest.Timestamp = time.Now()
inst.Guest.Heartbeat = time.Now()
}
data := &instancesData{
Instances: demo.Instances,
Count: int64(len(demo.Instances)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
ndeId, _ := utils.ParseObjectId(c.Query("node_names"))
plId, _ := utils.ParseObjectId(c.Query("pool_names"))
if !ndeId.IsZero() {
query := &bson.M{
"node": ndeId,
"organization": userOrg,
}
insts, err := instance.GetAllName(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, insts)
} else if !plId.IsZero() {
nodes, err := node.GetAllPool(db, plId)
if err != nil {
return
}
ndeIds := []bson.ObjectID{}
for _, nde := range nodes {
ndeIds = append(ndeIds, nde.Id)
}
query := &bson.M{
"node": &bson.M{
"$in": ndeIds,
},
"organization": userOrg,
}
insts, err := instance.GetAllName(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, insts)
} else {
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
instId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = instId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
role := strings.TrimSpace(c.Query("role"))
if role != "" {
if strings.HasPrefix(role, "~") {
role := role[1:]
if strings.HasPrefix(role, "!") {
query["roles"] = &bson.M{
"$not": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role[1:])),
"$options": "i",
},
}
} else {
query["$or"] = []*bson.M{
&bson.M{
"roles": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role)),
"$options": "i",
},
},
}
}
} else {
if strings.HasPrefix(role, "!") {
role = strings.TrimLeft(role, "!")
query["roles"] = &bson.M{
"$ne": role,
}
} else {
query["roles"] = role
}
}
}
networkNamespace := strings.TrimSpace(c.Query("network_namespace"))
if networkNamespace != "" {
query["network_namespace"] = networkNamespace
}
nodeId, ok := utils.ParseObjectId(c.Query("node"))
if ok {
query["node"] = nodeId
}
zoneId, ok := utils.ParseObjectId(c.Query("zone"))
if ok {
query["zone"] = zoneId
}
vpcId, ok := utils.ParseObjectId(c.Query("vpc"))
if ok {
query["vpc"] = vpcId
}
subnetId, ok := utils.ParseObjectId(c.Query("subnet"))
if ok {
query["subnet"] = subnetId
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
instances, count, err := instance.GetAllPaged(
db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, inst := range instances {
inst.Json(false)
if demo.IsDemo() {
inst.State = vm.Running
inst.Action = instance.Start
inst.Status = "Running"
inst.PublicIps = []string{
demo.RandIp(inst.Id),
}
inst.PublicIps6 = []string{
demo.RandIp6(inst.Id),
}
inst.PrivateIps = []string{
demo.RandPrivateIp(inst.Id),
}
inst.PrivateIps6 = []string{
demo.RandPrivateIp6(inst.Id),
}
inst.NetworkNamespace = vm.GetNamespace(inst.Id, 0)
}
}
dta := &instancesData{
Instances: instances,
Count: count,
}
c.JSON(200, dta)
}
}
func instanceVncGet(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
instanceId, ok := utils.ParseObjectId(c.Param("instance_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
inst, err := instance.GetOrg(db, userOrg, instanceId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = inst.VncConnect(db, c.Writer, c.Request)
if err != nil {
if _, ok := err.(*instance.VncDialError); ok {
utils.AbortWithStatus(c, 504)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
}
================================================
FILE: uhandlers/license.go
================================================
package uhandlers
import (
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type licenseData struct {
Oracle bool `json:"oracle"`
}
func licensePut(c *gin.Context) {
if demo.IsDemo() {
c.JSON(200, nil)
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &licenseData{}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
usr.OracleLicense = data.Oracle
err = usr.CommitFields(db, set.NewSet("oracle_licese"))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, data)
return
}
================================================
FILE: uhandlers/node.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/zone"
)
func nodesGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
zoneStr := c.Query("zone")
if zoneStr == "" {
c.JSON(200, []interface{}{})
return
}
zneId, _ := utils.ParseObjectId(zoneStr)
zne, err := zone.Get(db, zneId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := datacenter.ExistsOrg(db, userOrg, zne.Datacenter)
if err != nil {
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
query := &bson.M{
"zone": zneId,
}
nodes, err := node.GetAllHypervisors(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, nodes)
}
================================================
FILE: uhandlers/organization.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/organization"
"github.com/pritunl/pritunl-cloud/utils"
)
func organizationsGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
orgs, err := organization.GetAllNameRoles(db, usr.Roles)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, orgs)
}
================================================
FILE: uhandlers/plan.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/plan"
"github.com/pritunl/pritunl-cloud/utils"
)
type planData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Statements []*plan.Statement `json:"statements"`
}
type plansData struct {
Plans []*plan.Plan `json:"plans"`
Count int64 `json:"count"`
}
func planPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &planData{}
planId, ok := utils.ParseObjectId(c.Param("plan_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
pln, err := plan.GetOrg(db, userOrg, planId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
pln.Name = data.Name
pln.Comment = data.Comment
err = pln.UpdateStatements(data.Statements)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
fields := set.NewSet(
"name",
"comment",
"statements",
)
errData, err := pln.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = pln.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "plan.change")
c.JSON(200, pln)
}
func planPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &planData{
Name: "new-plan",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
pln := &plan.Plan{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
}
err = pln.UpdateStatements(data.Statements)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
errData, err := pln.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = pln.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "plan.change")
c.JSON(200, pln)
}
func planDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
planId, ok := utils.ParseObjectId(c.Param("plan_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := plan.RemoveOrg(db, userOrg, planId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "plan.change")
c.JSON(200, nil)
}
func plansDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = plan.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "plan.change")
c.JSON(200, nil)
}
func planGet(c *gin.Context) {
if demo.IsDemo() {
pln := demo.Plans[0]
c.JSON(200, pln)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
planId, ok := utils.ParseObjectId(c.Param("plan_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
pln, err := plan.GetOrg(db, userOrg, planId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, pln)
}
func plansGet(c *gin.Context) {
if demo.IsDemo() {
data := &plansData{
Plans: demo.Plans,
Count: int64(len(demo.Plans)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
if c.Query("names") == "true" {
query := bson.M{
"organization": userOrg,
}
plns, err := plan.GetAllName(db, &query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, plns)
} else {
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
planId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = planId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
plans, count, err := plan.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &plansData{
Plans: plans,
Count: count,
}
c.JSON(200, data)
}
}
================================================
FILE: uhandlers/pod.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/aggregate"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/journal"
"github.com/pritunl/pritunl-cloud/pod"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/scheduler"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/pritunl-cloud/unit"
"github.com/pritunl/pritunl-cloud/utils"
)
type podData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Organization bson.ObjectID `json:"organization"`
DeleteProtection bool `json:"delete_protection"`
Units []*unit.UnitInput `json:"units"`
Drafts []*pod.UnitDraft `json:"drafts"`
Count int `json:"count"`
}
type podsData struct {
Pods []*aggregate.PodAggregate `json:"pods"`
Count int64 `json:"count"`
}
type podsDeployData struct {
Count int `json:"count"`
Spec bson.ObjectID `json:"spec"`
}
type deploymentData struct {
Id bson.ObjectID `json:"id"`
Tags []string `json:"tags"`
}
type specsData struct {
Specs []*spec.Named `json:"specs"`
Count int64 `json:"count"`
}
func podPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &podData{}
podId, ok := utils.ParseObjectId(c.Param("pod_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
pd, err := pod.GetOrg(db, userOrg, podId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
pd.Name = data.Name
pd.Comment = data.Comment
pd.DeleteProtection = data.DeleteProtection
fields := set.NewSet(
"name",
"comment",
"delete_protection",
)
errData, err := pd.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
errData, err = pd.CommitFieldsUnits(db, data.Units, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = pod.UpdateDrafts(db, podId, usr.Id, []*pod.UnitDraft{})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, pd)
}
func podDraftsPut(c *gin.Context) {
if demo.BlockedSilent(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &podData{}
podId, ok := utils.ParseObjectId(c.Param("pod_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
err = pod.UpdateDraftsOrg(db, userOrg, podId, usr.Id, data.Drafts)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, nil)
}
func podDeployPut(c *gin.Context) {
if demo.BlockedSilent(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &podData{}
podId, ok := utils.ParseObjectId(c.Param("pod_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
units, err := unit.GetAll(db, &bson.M{
"pod": podId,
"organization": userOrg,
})
if err != nil {
return
}
unitsDataMap := map[bson.ObjectID]*unit.UnitInput{}
for _, unitData := range data.Units {
unitsDataMap[unitData.Id] = unitData
}
for _, unt := range units {
unitData := unitsDataMap[unt.Id]
if unitData == nil || unitData.DeploySpec.IsZero() {
continue
}
deploySpec, e := spec.Get(db, unitData.DeploySpec)
if e != nil || deploySpec.Unit != unt.Id {
errData := &errortypes.ErrorData{
Error: "unit_deploy_spec_invalid",
Message: "Invalid unit deployment commit",
}
c.JSON(400, errData)
return
}
unt.DeploySpec = unitData.DeploySpec
err = unt.CommitFields(db, set.NewSet("deploy_spec"))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
}
c.JSON(200, nil)
}
func podPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &podData{
Name: "new-pod",
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
pd := &pod.Pod{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
DeleteProtection: data.DeleteProtection,
}
errData, err := pd.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = pd.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
errData, err = pd.InitUnits(db, data.Units)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, pd)
}
func podDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
podId, ok := utils.ParseObjectId(c.Param("pod_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
errData, err := relations.CanDeleteOrg(db, "pod", userOrg, podId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = pod.RemoveOrg(db, userOrg, podId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, nil)
}
func podsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
errData, err := relations.CanDeleteOrgAll(db, "pod", userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = pod.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, nil)
}
func podGet(c *gin.Context) {
if demo.IsDemo() {
pd := demo.Pods[0]
c.JSON(200, pd)
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
userOrg := c.MustGet("organization").(bson.ObjectID)
podId, ok := utils.ParseObjectId(c.Param("pod_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
pd, err := aggregate.GetPod(db, usr.Id, &bson.M{
"_id": podId,
"organization": userOrg,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
c.JSON(200, pd)
}
func podsGet(c *gin.Context) {
if demo.IsDemo() {
data := &podsData{
Pods: demo.Pods,
Count: int64(len(demo.Pods)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
podId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = podId
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
role := strings.TrimSpace(c.Query("role"))
if role != "" {
if strings.HasPrefix(role, "~") {
role := role[1:]
if strings.HasPrefix(role, "!") {
query["roles"] = &bson.M{
"$not": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role[1:])),
"$options": "i",
},
}
} else {
query["$or"] = []*bson.M{
&bson.M{
"roles": &bson.M{
"$regex": fmt.Sprintf(".*%s.*",
regexp.QuoteMeta(role)),
"$options": "i",
},
},
}
}
} else {
if strings.HasPrefix(role, "!") {
role = strings.TrimLeft(role, "!")
query["roles"] = &bson.M{
"$ne": role,
}
} else {
query["roles"] = role
}
}
}
pods, count, err := aggregate.GetPodsPaged(db, usr.Id,
&query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &podsData{
Pods: pods,
Count: count,
}
c.JSON(200, data)
}
type PodUnit struct {
Id bson.ObjectID `json:"id"`
Pod bson.ObjectID `json:"pod"`
Kind string `json:"kind"`
Deployments []*aggregate.Deployment `json:"deployments"`
}
func podUnitGet(c *gin.Context) {
if demo.IsDemo() {
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
var unit *unit.Unit
for _, unt := range demo.Units {
if unt.Id == unitId {
unit = unt
break
}
}
deplys := []*aggregate.Deployment{}
for _, deply := range demo.Deployments {
if deply.Unit == unit.Id {
deplys = append(deplys, deply)
}
}
data := &PodUnit{
Id: unit.Id,
Pod: demo.Pods[0].Id,
Kind: unit.Kind,
Deployments: deplys,
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
unt, err := unit.GetOrg(db, userOrg, unitId)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
deploys, err := aggregate.GetDeployments(db, unt)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
pdUnit := &PodUnit{
Id: unt.Id,
Pod: unt.Pod,
Kind: unt.Kind,
Deployments: deploys,
}
c.JSON(200, pdUnit)
}
func podUnitDeploymentsPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
unt, err := unit.GetOrg(db, userOrg, unitId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
action := c.Query("action")
switch action {
case deployment.Archive:
err = deployment.ArchiveMulti(db, unt.Id, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
break
case deployment.Restore:
err = deployment.RestoreMulti(db, unt.Id, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
break
case deployment.Destroy:
err = deployment.RemoveMulti(db, unt.Id, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
break
case deployment.Migrate:
commitId, ok := utils.ParseObjectId(c.Query("commit"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
errData, err := unt.MigrateDeployements(db, commitId, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
break
}
event.PublishDispatch(db, "instance.change")
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, nil)
}
func podUnitDeploymentPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &podsDeployData{}
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
unt, err := unit.GetOrg(db, userOrg, unitId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
errData, err := scheduler.ManualSchedule(db, unt, data.Spec, data.Count)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
event.PublishDispatch(db, "instance.change")
event.PublishDispatch(db, "pod.change")
event.PublishDispatch(db, "unit.change")
c.JSON(200, nil)
}
func podUnitDeploymentPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &deploymentData{}
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
deplyId, ok := utils.ParseObjectId(c.Param("deployment_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
deply, err := deployment.GetUnitOrg(db, userOrg, unitId, deplyId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
deply.Tags = data.Tags
fields := set.NewSet(
"tags",
)
errData, err := deply.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = deply.CommitFields(db, fields)
if err != nil {
return
}
event.PublishDispatch(db, "instance.change")
event.PublishDispatch(db, "pod.change")
c.JSON(200, nil)
}
func podUnitDeploymentLogGet(c *gin.Context) {
if demo.IsDemo() {
c.JSON(200, demo.DeploymentLogs)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
deplyId, ok := utils.ParseObjectId(c.Param("deployment_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
deply, err := deployment.GetUnitOrg(db, userOrg, unitId, deplyId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
kind := int32(0)
resource := c.Query("resource")
if resource == "agent" {
kind = journal.DeploymentAgent
}
for _, jrnl := range deply.Journals {
if jrnl.Key == resource {
kind = jrnl.Index
}
}
if kind == 0 {
utils.AbortWithStatus(c, 404)
return
}
data, err := journal.GetOutput(c, db, deply.Id, kind)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
c.JSON(200, data)
}
func podUnitSpecsGet(c *gin.Context) {
if demo.IsDemo() {
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
specs := []*spec.Named{}
for _, spc := range demo.SpecsNamed {
if spc.Unit == unitId {
specs = append(specs, spc)
}
}
data := &specsData{
Specs: specs,
Count: int64(len(specs)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
specs, count, err := spec.GetAllPaged(db, &bson.M{
"unit": unitId,
"organization": userOrg,
}, page, pageCount)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
data := &specsData{
Specs: specs,
Count: count,
}
c.JSON(200, data)
}
func podUnitSpecGet(c *gin.Context) {
if demo.IsDemo() {
specId, ok := utils.ParseObjectId(c.Param("spec_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
for _, spc := range demo.Specs {
if spc.Id == specId {
c.JSON(200, spc)
return
}
}
c.AbortWithStatus(404)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
unitId, ok := utils.ParseObjectId(c.Param("unit_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
specId, ok := utils.ParseObjectId(c.Param("spec_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
spec, err := spec.GetOne(db, &bson.M{
"_id": specId,
"unit": unitId,
"organization": userOrg,
})
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
c.AbortWithStatus(404)
} else {
utils.AbortWithError(c, 500, err)
}
return
}
c.JSON(200, spec)
}
================================================
FILE: uhandlers/pool.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/pool"
"github.com/pritunl/pritunl-cloud/utils"
)
func poolsGet(c *gin.Context) {
if demo.IsDemo() {
c.JSON(200, demo.Pools)
return
}
db := c.MustGet("db").(*database.Database)
pools, err := pool.GetAllNames(db, &bson.M{})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, pools)
}
================================================
FILE: uhandlers/relations.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/utils"
)
type relationsData struct {
Id any `json:"id"`
Kind string `json:"kind"`
Data string `json:"data"`
}
func relationsGet(c *gin.Context) {
if demo.IsDemo() {
kind := c.Param("kind")
resourceId, ok := utils.ParseObjectId(c.Param("id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
data := &relationsData{
Id: resourceId,
Kind: kind,
Data: "demo",
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
kind := c.Param("kind")
resourceId, ok := utils.ParseObjectId(c.Param("id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
resp, err := relations.AggregateOrg(db, kind, userOrg, resourceId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if resp == nil {
utils.AbortWithStatus(c, 404)
return
}
data := &relationsData{
Id: resp.Id,
Kind: kind,
Data: resp.Yaml(),
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/secret.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/secret"
"github.com/pritunl/pritunl-cloud/utils"
)
type secretData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Type string `json:"type"`
Key string `json:"key"`
Value string `json:"value"`
Data string `json:"data"`
Region string `json:"region"`
}
type secretsData struct {
Secrets []*secret.Secret `json:"secrets"`
Count int64 `json:"count"`
}
func secretPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &secretData{}
secrId, ok := utils.ParseObjectId(c.Param("secr_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secr, err := secret.GetOrg(db, userOrg, secrId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secr.Name = data.Name
secr.Comment = data.Comment
secr.Type = data.Type
secr.Key = data.Key
secr.Value = data.Value
secr.Data = data.Data
secr.Region = data.Region
fields := set.NewSet(
"name",
"comment",
"type",
"key",
"value",
"data",
"region",
"public_key",
"private_key",
)
errData, err := secr.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = secr.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "secret.change")
c.JSON(200, secr)
}
func secretPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &secretData{
Name: "new-secret",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
secr := &secret.Secret{
Name: data.Name,
Comment: data.Comment,
Organization: userOrg,
Type: data.Type,
Key: data.Key,
Value: data.Value,
Data: data.Data,
Region: data.Region,
}
errData, err := secr.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = secr.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "secret.change")
c.JSON(200, secr)
}
func secretDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
secrId, ok := utils.ParseObjectId(c.Param("secr_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
errData, err := relations.CanDeleteOrg(db, "secret", userOrg, secrId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = secret.RemoveOrg(db, userOrg, secrId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "secret.change")
c.JSON(200, nil)
}
func secretsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Bind error"),
}
utils.AbortWithError(c, 500, err)
return
}
errData, err := relations.CanDeleteOrgAll(db, "secret", userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = secret.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "secret.change")
c.JSON(200, nil)
}
func secretGet(c *gin.Context) {
if demo.IsDemo() {
secr := demo.Secrets[0]
c.JSON(200, secr)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
secrId, ok := utils.ParseObjectId(c.Param("secr_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
secr, err := secret.GetOrg(db, userOrg, secrId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if demo.IsDemo() {
secr.Key = "demo"
secr.Value = "demo"
}
c.JSON(200, secr)
}
func secretsGet(c *gin.Context) {
if demo.IsDemo() {
data := &secretsData{
Secrets: demo.Secrets,
Count: int64(len(demo.Secrets)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
secretId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = secretId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
comment := strings.TrimSpace(c.Query("comment"))
if comment != "" {
query["comment"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", comment),
"$options": "i",
}
}
secrs, count, err := secret.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
data := &secretsData{
Secrets: secrs,
Count: count,
}
c.JSON(200, data)
}
================================================
FILE: uhandlers/shape.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/shape"
"github.com/pritunl/pritunl-cloud/utils"
)
func shapesGet(c *gin.Context) {
if demo.IsDemo() {
c.JSON(200, demo.Shapes)
return
}
db := c.MustGet("db").(*database.Database)
shapes, err := shape.GetAllNames(db, &bson.M{})
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, shapes)
}
================================================
FILE: uhandlers/static.go
================================================
package uhandlers
import (
"strings"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/auth"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/config"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/middlewear"
"github.com/pritunl/pritunl-cloud/static"
"github.com/pritunl/pritunl-cloud/utils"
)
func staticPath(c *gin.Context, pth string, cache bool) {
pth = config.StaticRoot + pth
file, ok := store.Files[pth]
if !ok {
utils.AbortWithStatus(c, 404)
return
}
if constants.StaticCache && cache {
c.Writer.Header().Add("Cache-Control", "public, max-age=86400")
c.Writer.Header().Add("ETag", file.Hash)
} else {
c.Writer.Header().Add("Cache-Control",
"no-cache, no-store, must-revalidate")
c.Writer.Header().Add("Pragma", "no-cache")
c.Writer.Header().Add("Expires", "0")
}
if strings.Contains(c.Request.Header.Get("Accept-Encoding"), "gzip") {
c.Writer.Header().Add("Content-Encoding", "gzip")
c.Data(200, file.Type, file.GzipData)
} else {
c.Data(200, file.Type, file.Data)
}
}
func staticIndexGet(c *gin.Context) {
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
fastPth := auth.GetFastUserPath()
if fastPth != "" {
c.Redirect(302, fastPth)
return
}
c.Redirect(302, "/login")
return
}
staticPath(c, "/uindex.html", false)
}
func staticLoginGet(c *gin.Context) {
fastPth := auth.GetFastUserPath()
if fastPth != "" {
c.Redirect(302, fastPth)
return
}
staticPath(c, "/login.html", false)
}
func staticLogoGet(c *gin.Context) {
staticPath(c, "/logo.png", true)
}
func staticGet(c *gin.Context) {
staticPath(c, "/static"+c.Params.ByName("path"), true)
}
func staticTestingGet(c *gin.Context) {
pth := c.Params.ByName("path")
if pth == "" {
if c.Request.URL.Path == "/config.js" {
pth = "config.js"
} else if c.Request.URL.Path == "/logo.png" {
pth = "logo.png"
} else if c.Request.URL.Path == "/build.js" {
pth = "build.js"
} else if c.Request.URL.Path == "/login" {
fastPth := auth.GetFastUserPath()
if fastPth != "" {
c.Redirect(302, fastPth)
return
}
c.Request.URL.Path = "/login.html"
pth = "login.html"
} else {
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
if !authr.IsValid() {
fastPth := auth.GetFastUserPath()
if fastPth != "" {
c.Redirect(302, fastPth)
return
}
c.Redirect(302, "/login")
return
}
pth = "uindex.html"
}
}
if strings.HasPrefix(c.Request.URL.Path, "/node_modules/") ||
strings.HasPrefix(c.Request.URL.Path, "/jspm_packages/") {
c.Writer.Header().Add("Cache-Control", "public, max-age=86400")
} else {
c.Writer.Header().Add("Cache-Control",
"no-cache, no-store, must-revalidate")
c.Writer.Header().Add("Pragma", "no-cache")
c.Writer.Header().Add("Expires", "0")
}
if c.Request.URL.Path == "/" {
c.Request.URL.Path = "/uindex.html"
}
c.Writer.Header().Add("Content-Type", static.GetMimeType(pth))
gzipWriter := middlewear.NewGzipWriter(c)
defer gzipWriter.Close()
fileServer.ServeHTTP(gzipWriter, c.Request)
}
================================================
FILE: uhandlers/theme.go
================================================
package uhandlers
import (
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/pritunl-cloud/authorizer"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/utils"
)
type themeData struct {
Theme string `json:"theme"`
EditorTheme string `json:"editor_theme"`
}
func themePut(c *gin.Context) {
if demo.IsDemo() {
c.JSON(200, nil)
return
}
db := c.MustGet("db").(*database.Database)
authr := c.MustGet("authorizer").(*authorizer.Authorizer)
data := &themeData{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
usr, err := authr.GetUser(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
usr.Theme = data.Theme
usr.EditorTheme = data.EditorTheme
err = usr.CommitFields(db, set.NewSet("theme", "editor_theme"))
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, data)
return
}
================================================
FILE: uhandlers/utils.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
)
type redirectData struct {
Redirect string `json:"redirect"`
}
func redirectQuery(c *gin.Context, query string) {
if query != "" {
c.Redirect(302, "/?"+query)
} else {
c.Redirect(302, "/"+query)
}
}
func redirectQueryJson(c *gin.Context, query string) {
data := redirectData{
Redirect: "/",
}
if query != "" {
data.Redirect += "?" + query
}
c.JSON(202, data)
}
================================================
FILE: uhandlers/vpc.go
================================================
package uhandlers
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/demo"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/relations"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vpc"
)
type vpcData struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Comment string `json:"comment"`
Network string `json:"network"`
IcmpRedirects bool `json:"icmp_redirects"`
Subnets []*vpc.Subnet `json:"subnets"`
Datacenter bson.ObjectID `json:"datacenter"`
Routes []*vpc.Route `json:"routes"`
Maps []*vpc.Map `json:"maps"`
}
type vpcsData struct {
Vpcs []*vpc.Vpc `json:"vpcs"`
Count int64 `json:"count"`
}
func vpcPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &vpcData{}
vpcId, ok := utils.ParseObjectId(c.Param("vpc_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := datacenter.ExistsOrg(db, userOrg, data.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
vc, err := vpc.GetOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if vc.Organization != userOrg {
utils.AbortWithStatus(c, 405)
return
}
vc.PreCommit()
vc.Name = data.Name
vc.Comment = data.Comment
vc.IcmpRedirects = data.IcmpRedirects
vc.Routes = data.Routes
vc.Maps = data.Maps
vc.Subnets = data.Subnets
fields := set.NewSet(
"name",
"comment",
"icmp_redirects",
"routes",
"maps",
"subnets",
)
errData, err := vc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
errData, err = vc.PostCommit(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = vc.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "vpc.change")
vc.Json()
c.JSON(200, vc)
}
func vpcPost(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := &vpcData{
Name: "new-vpc",
}
err := c.Bind(data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
exists, err := datacenter.ExistsOrg(db, userOrg, data.Datacenter)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
vc := &vpc.Vpc{
Name: data.Name,
Comment: data.Comment,
Network: data.Network,
Subnets: data.Subnets,
Organization: userOrg,
Datacenter: data.Datacenter,
IcmpRedirects: data.IcmpRedirects,
Routes: data.Routes,
Maps: data.Maps,
}
vc.InitVpc()
errData, err := vc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = vc.Insert(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "vpc.change")
vc.Json()
c.JSON(200, vc)
}
func vpcDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
vpcId, ok := utils.ParseObjectId(c.Param("vpc_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
exists, err := vpc.ExistsOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
errData, err := relations.CanDeleteOrg(db, "vpc", userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = vpc.RemoveOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "vpc.change")
c.JSON(200, nil)
}
func vpcsDelete(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []bson.ObjectID{}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, vpcId := range data {
exists, e := vpc.ExistsOrg(db, userOrg, vpcId)
if e != nil {
utils.AbortWithError(c, 500, e)
return
}
if !exists {
utils.AbortWithStatus(c, 405)
return
}
}
errData, err := relations.CanDeleteOrgAll(db, "vpc", userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = vpc.RemoveMultiOrg(db, userOrg, data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "vpc.change")
c.JSON(200, nil)
}
func vpcGet(c *gin.Context) {
if demo.IsDemo() {
vc := demo.Vpcs[0]
c.JSON(200, vc)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
vpcId, ok := utils.ParseObjectId(c.Param("vpc_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
vc, err := vpc.GetOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
vc.Json()
c.JSON(200, vc)
}
func vpcRoutesGet(c *gin.Context) {
if demo.IsDemo() {
vc := demo.Vpcs[0]
c.JSON(200, vc.Routes)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
vpcId, ok := utils.ParseObjectId(c.Param("vpc_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
vc, err := vpc.GetOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, vc.Routes)
}
func vpcRoutesPut(c *gin.Context) {
if demo.Blocked(c) {
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
data := []*vpc.Route{}
vpcId, ok := utils.ParseObjectId(c.Param("vpc_id"))
if !ok {
utils.AbortWithStatus(c, 400)
return
}
err := c.Bind(&data)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
vc, err := vpc.GetOrg(db, userOrg, vpcId)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
vc.Routes = data
fields := set.NewSet(
"routes",
)
errData, err := vc.Validate(db)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
if errData != nil {
c.JSON(400, errData)
return
}
err = vc.CommitFields(db, fields)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
event.PublishDispatch(db, "vpc.change")
vc.Json()
c.JSON(200, vc)
}
func vpcsGet(c *gin.Context) {
if demo.IsDemo() {
data := &vpcsData{
Vpcs: demo.Vpcs,
Count: int64(len(demo.Vpcs)),
}
c.JSON(200, data)
return
}
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
if c.Query("names") == "true" {
query := &bson.M{
"organization": userOrg,
}
vpcs, err := vpc.GetAllNames(db, query)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, vpcs)
} else {
page, _ := strconv.ParseInt(c.Query("page"), 10, 0)
pageCount, _ := strconv.ParseInt(c.Query("page_count"), 10, 0)
query := bson.M{
"organization": userOrg,
}
vpcId, ok := utils.ParseObjectId(c.Query("id"))
if ok {
query["_id"] = vpcId
}
name := strings.TrimSpace(c.Query("name"))
if name != "" {
query["name"] = &bson.M{
"$regex": fmt.Sprintf(".*%s.*", regexp.QuoteMeta(name)),
"$options": "i",
}
}
network := strings.TrimSpace(c.Query("network"))
if network != "" {
query["network"] = network
}
dc, ok := utils.ParseObjectId(c.Query("datacenter"))
if ok {
query["datacenter"] = dc
}
vpcs, count, err := vpc.GetAllPaged(db, &query, page, pageCount)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
for _, vc := range vpcs {
vc.Json()
}
data := &vpcsData{
Vpcs: vpcs,
Count: count,
}
c.JSON(200, data)
}
}
================================================
FILE: uhandlers/zone.go
================================================
package uhandlers
import (
"github.com/gin-gonic/gin"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/datacenter"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/zone"
)
func zonesGet(c *gin.Context) {
db := c.MustGet("db").(*database.Database)
userOrg := c.MustGet("organization").(bson.ObjectID)
dcIds, err := datacenter.DistinctOrg(db, userOrg)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
zones, err := zone.GetAllNamedDc(db, dcIds)
if err != nil {
utils.AbortWithError(c, 500, err)
return
}
c.JSON(200, zones)
}
================================================
FILE: unit/unit.go
================================================
package unit
import (
"sync"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/deployment"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/spec"
"github.com/pritunl/tools/errors"
)
type Unit struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Pod bson.ObjectID `bson:"pod" json:"pod"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Name string `bson:"name" json:"name"`
Kind string `bson:"kind" json:"kind"`
Count int `bson:"count" json:"count"`
Deployments []bson.ObjectID `bson:"deployments" json:"deployments"`
Spec string `bson:"spec" json:"spec"`
SpecIndex int `bson:"spec_index" json:"spec_index"`
SpecTimestamp time.Time `bson:"spec_timestamp" json:"-"`
LastSpec bson.ObjectID `bson:"last_spec" json:"last_spec"`
DeploySpec bson.ObjectID `bson:"deploy_spec" json:"deploy_spec"`
Hash string `bson:"hash" json:"hash"`
Journals map[string]int32 `bson:"journals" json:"-"`
JournalsIndex int32 `bson:"journals_index" json:"-"`
journalsLock sync.Mutex `bson:"-" json:"-"`
newUnit bool `bson:"-" json:"-"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Pod bson.ObjectID `bson:"pod" json:"pod"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Name string `bson:"name" json:"name"`
Kind string `bson:"kind" json:"kind"`
}
type UnitInput struct {
Id bson.ObjectID `json:"id"`
Name string `json:"name"`
Spec string `json:"spec"`
DeploySpec bson.ObjectID `json:"deploy_spec"`
Delete bool `json:"delete"`
}
func (u *Unit) Refresh(db *database.Database) (err error) {
coll := db.Units()
unt := &Unit{}
err = coll.FindOne(db, &bson.M{
"_id": u.Id,
}).Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
u.Id = unt.Id
u.Pod = unt.Pod
u.Organization = unt.Organization
u.Name = unt.Name
u.Kind = unt.Kind
u.Count = unt.Count
u.Deployments = unt.Deployments
u.Spec = unt.Spec
u.SpecIndex = unt.SpecIndex
u.SpecTimestamp = unt.SpecTimestamp
u.LastSpec = unt.LastSpec
u.DeploySpec = unt.DeploySpec
u.Hash = unt.Hash
u.Journals = unt.Journals
u.JournalsIndex = unt.JournalsIndex
return
}
func (u *Unit) RefreshJournals(db *database.Database) (err error) {
coll := db.Units()
unt := &Unit{}
err = coll.FindOne(db, &bson.M{
"_id": u.Id,
}).Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
u.journalsLock.Lock()
u.Journals = unt.Journals
u.JournalsIndex = unt.JournalsIndex
u.journalsLock.Unlock()
return
}
func (u *Unit) HasDeployment(deployId bson.ObjectID) bool {
if u.Deployments != nil {
for _, deplyId := range u.Deployments {
if deplyId == deployId {
return true
}
}
}
return false
}
func (u *Unit) Reserve(db *database.Database, deployId bson.ObjectID,
overrideCount int) (reserved bool, err error) {
coll := db.Units()
if overrideCount == 0 {
if len(u.Deployments) >= u.Count {
return
}
} else {
if len(u.Deployments) >= overrideCount {
return
}
}
resp, err := coll.UpdateOne(db, bson.M{
"_id": u.Id,
"pod": u.Pod,
"count": u.Count,
"deployments": bson.M{
"$size": len(u.Deployments),
},
}, bson.M{
"$push": bson.M{
"deployments": deployId,
},
})
if err != nil {
err = database.ParseError(err)
return
}
if resp.MatchedCount == 1 && resp.ModifiedCount == 1 {
reserved = true
}
return
}
func (u *Unit) RestoreDeployment(db *database.Database,
deployId bson.ObjectID) (err error) {
coll := db.Units()
_, err = coll.UpdateOne(db, bson.M{
"_id": u.Id,
}, bson.M{
"$push": bson.M{
"deployments": deployId,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (u *Unit) RemoveDeployement(db *database.Database,
deployId bson.ObjectID) (err error) {
coll := db.Units()
_, err = coll.UpdateOne(db, bson.M{
"_id": u.Id,
}, bson.M{
"$pull": bson.M{
"deployments": deployId,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (u *Unit) MigrateDeployements(db *database.Database,
newSpecId bson.ObjectID, deplyIds []bson.ObjectID) (
errData *errortypes.ErrorData, err error) {
coll := db.Deployments()
newSpc, err := spec.Get(db, newSpecId)
if err != nil {
return
}
if newSpc.Pod != u.Pod || newSpc.Unit != u.Id {
err = &errortypes.ParseError{
errors.Newf("spec: Invalid unit"),
}
return
}
deplys, err := deployment.GetAll(db, &bson.M{
"_id": &bson.M{
"$in": deplyIds,
},
"pod": u.Pod,
"unit": u.Id,
})
if err != nil {
return
}
spcMap := map[bson.ObjectID]*spec.Spec{}
for _, deply := range deplys {
oldSpc := spcMap[deply.Spec]
if oldSpc == nil {
oldSpc, err = spec.Get(db, deply.Spec)
if err != nil {
return
}
spcMap[oldSpc.Id] = oldSpc
}
errData, err = oldSpc.CanMigrate(db, deply, newSpc)
if err != nil || errData != nil {
return
}
}
_, err = coll.UpdateMany(db, &bson.M{
"_id": &bson.M{
"$in": deplyIds,
},
"pod": u.Pod,
"unit": u.Id,
}, &bson.M{
"$set": &bson.M{
"action": deployment.Migrate,
"new_spec": newSpc.Id,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (u *Unit) newSpec(db *database.Database, spc *spec.Spec, newUnit bool) (
newSpec *spec.Spec, errData *errortypes.ErrorData, err error) {
u.Name = spc.Name
u.Count = spc.Count
u.Spec = spc.Data
if u.Kind == "" {
u.Kind = spc.Kind
} else if u.Kind != spc.Kind {
errData = &errortypes.ErrorData{
Error: "spec_kind_invalid",
Message: "Cannot change spec kind",
}
return
}
if newUnit {
spc.Index = 1
spc.Timestamp = time.Now()
} else {
timestamp, index, e := NewSpec(db, u.Pod, u.Id)
if e != nil {
err = e
return
}
spc.Index = index
spc.Timestamp = timestamp
}
newSpec = spc
u.Hash = spc.Hash
u.LastSpec = spc.Id
if u.DeploySpec.IsZero() {
u.DeploySpec = spc.Id
}
return
}
func (u *Unit) updateSpec(db *database.Database, spc *spec.Spec) (
updateSpec *spec.Spec, errData *errortypes.ErrorData, err error) {
curSpc, e := spec.Get(db, u.LastSpec)
if e != nil {
err = e
return
}
curSpc.Name = spc.Name
curSpc.Count = spc.Count
curSpc.Data = spc.Data
updateSpec = curSpc
u.Name = curSpc.Name
u.Count = curSpc.Count
u.Spec = curSpc.Data
if u.Kind == "" {
u.Kind = spc.Kind
} else if u.Kind != spc.Kind {
errData = &errortypes.ErrorData{
Error: "spec_kind_invalid",
Message: "Cannot change spec kind",
}
return
}
u.Hash = curSpc.Hash
u.LastSpec = curSpc.Id
if u.DeploySpec.IsZero() {
u.DeploySpec = curSpc.Id
}
return
}
func (u *Unit) getKind(db *database.Database, key string) (
kind int32, err error) {
u.journalsLock.Lock()
defer u.journalsLock.Unlock()
if u.Journals == nil {
u.Journals = map[string]int32{}
}
kind, ok := u.Journals[key]
if ok && kind != 0 {
return
}
jrnls := map[string]int32{}
for key, index := range u.Journals {
jrnls[key] = index
}
index := u.JournalsIndex
if index == 0 {
index = 248000
}
index += 1
jrnls[key] = index
coll := db.Units()
query := bson.M{
"_id": u.Id,
}
if u.JournalsIndex == 0 {
query["$or"] = []bson.M{
{"journals_index": bson.M{"$exists": false}},
{"journals_index": 0},
}
} else {
query["journals_index"] = u.JournalsIndex
}
if !u.newUnit {
resp, e := coll.UpdateOne(db, query, bson.M{
"$set": bson.M{
"journals_index": index,
"journals": jrnls,
},
})
if e != nil {
err = database.ParseError(e)
return
}
if resp.ModifiedCount < 1 {
kind = 0
return
}
}
u.Journals = jrnls
u.JournalsIndex = index
kind = index
return
}
func (u *Unit) GetKind(db *database.Database, key string) (
kind int32, err error) {
for i := 0; i < 3; i++ {
kind, err = u.getKind(db, key)
if err != nil {
return
}
if kind != 0 {
break
}
err = u.RefreshJournals(db)
if err != nil {
return
}
}
if kind == 0 {
err = &errortypes.ParseError{
errors.New("unit: Failed to get journal kind index"),
}
return
}
return
}
func (u *Unit) Parse(db *database.Database, newUnit bool) (
newSpec *spec.Spec, updateSpec *spec.Spec,
errData *errortypes.ErrorData, err error) {
if newUnit {
u.newUnit = true
}
spc := spec.New(u.Pod, u.Id, u.Organization, u.Spec)
errData, err = spc.Parse(db, u)
if err != nil {
return
}
if errData != nil {
return
}
isNewSpec := u.Hash != spc.Hash
if !isNewSpec && u.Name != spc.Name || u.Count != spc.Count {
updateSpec, errData, err = u.updateSpec(db, spc)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
err = nil
isNewSpec = true
} else {
return
}
}
if errData != nil {
return
}
}
if isNewSpec {
newSpec, errData, err = u.newSpec(db, spc, newUnit)
if err != nil {
return
}
if errData != nil {
return
}
}
return
}
func (u *Unit) Commit(db *database.Database) (err error) {
coll := db.Units()
err = coll.Commit(u.Id, u)
if err != nil {
return
}
return
}
func (u *Unit) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Units()
err = coll.CommitFields(u.Id, u, fields)
if err != nil {
return
}
return
}
func (u *Unit) Insert(db *database.Database) (err error) {
coll := db.Units()
if u.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("unit: Cannot insert unit without id"),
}
return
}
resp, err := coll.InsertOne(db, u)
if err != nil {
err = database.ParseError(err)
return
}
u.Id = resp.InsertedID.(bson.ObjectID)
return
}
================================================
FILE: unit/utils.go
================================================
package unit
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
)
func Get(db *database.Database, unitId bson.ObjectID) (
unt *Unit, err error) {
coll := db.Units()
unt = &Unit{}
err = coll.FindOneId(unitId, unt)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, unitId bson.ObjectID) (
unt *Unit, err error) {
coll := db.Units()
unt = &Unit{}
err = coll.FindOne(db, &bson.M{
"_id": unitId,
"organization": orgId,
}).Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M) (units []*Unit, err error) {
coll := db.Units()
units = []*Unit{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
unt := &Unit{}
err = cursor.Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
units = append(units, unt)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllMap(db *database.Database, query *bson.M) (
unitsMap map[bson.ObjectID]*Unit, err error) {
coll := db.Units()
unitsMap = map[bson.ObjectID]*Unit{}
cursor, err := coll.Find(db, query)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
unt := &Unit{}
err = cursor.Decode(unt)
if err != nil {
err = database.ParseError(err)
return
}
unitsMap[unt.Id] = unt
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func NewSpec(db *database.Database,
podId, unitId bson.ObjectID) (timestamp time.Time,
index int, err error) {
coll := db.Units()
updateOpts := options.FindOneAndUpdate().
SetProjection(&bson.M{
"spec_index": 1,
"spec_timestamp": 1,
}).
SetReturnDocument(options.After)
unit := &Unit{}
err = coll.FindOneAndUpdate(db, &bson.M{
"_id": unitId,
"pod": podId,
}, &bson.M{
"$inc": &bson.M{
"spec_index": 1,
},
"$currentDate": &bson.M{
"spec_timestamp": true,
},
}, updateOpts).Decode(unit)
if err != nil {
err = database.ParseError(err)
return
}
index = unit.SpecIndex
timestamp = unit.SpecTimestamp
return
}
func Remove(db *database.Database, untId bson.ObjectID) (err error) {
coll := db.Schedulers()
_, err = coll.DeleteOne(db, &bson.M{
"_id": untId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
coll = db.Units()
_, err = coll.DeleteOne(db, &bson.M{
"_id": untId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveOrg(db *database.Database, orgId, untId bson.ObjectID) (
err error) {
coll := db.Schedulers()
_, err = coll.DeleteOne(db, &bson.M{
"_id": untId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
coll = db.Units()
_, err = coll.DeleteOne(db, &bson.M{
"_id": untId,
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveAll(db *database.Database, query *bson.M) (err error) {
coll := db.Schedulers()
_, err = coll.DeleteMany(db, query)
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
coll = db.Units()
_, err = coll.DeleteMany(db, query)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMulti(db *database.Database,
untIds []bson.ObjectID) (err error) {
coll := db.Schedulers()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": untIds,
},
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
coll = db.Units()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": untIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
untIds []bson.ObjectID) (err error) {
coll := db.Schedulers()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": untIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
coll = db.Units()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": untIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/created.go
================================================
package upgrade
import (
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/disk"
"github.com/pritunl/pritunl-cloud/instance"
)
func createdUpgrade(db *database.Database) (err error) {
insts, err := instance.GetAll(db, &bson.M{
"created": &bson.M{
"$exists": false,
},
})
if err != nil {
return
}
for _, inst := range insts {
inst.Created = inst.Id.Timestamp()
err = inst.CommitFields(db, set.NewSet("created"))
if err != nil {
return
}
}
disks, err := disk.GetAll(db, &bson.M{
"created": &bson.M{
"$exists": false,
},
})
if err != nil {
return
}
for _, disk := range disks {
disk.Created = disk.Id.Timestamp()
err = disk.CommitFields(db, set.NewSet("created"))
if err != nil {
return
}
}
return
}
================================================
FILE: upgrade/instance.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
func instanceUpgrade(db *database.Database) (err error) {
coll := db.Instances()
_, err = coll.UpdateMany(db, bson.M{
"virt_timestamp": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"timestamp": "$virt_timestamp",
},
},
bson.M{
"$unset": "virt_timestamp",
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/journal.go
================================================
package upgrade
import (
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/journal"
)
func journalUpgrade(db *database.Database) (err error) {
coll := db.Journal()
cursor, err := coll.Find(db, &bson.M{
"c": &bson.M{
"$exists": false,
},
}, options.Find().
SetSort(bson.D{
{"t", 1},
{"_id", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
var count int32
var lastTime time.Time
i := 0
for cursor.Next(db) {
jrnl := &journal.Journal{}
err = cursor.Decode(jrnl)
if err != nil {
err = database.ParseError(err)
return
}
if jrnl.Timestamp.Unix() != lastTime.Unix() {
count = 1
}
lastTime = jrnl.Timestamp
if i%1000 == 0 {
println(count)
}
i += 1
_, err = coll.UpdateOne(db, &bson.M{
"_id": jrnl.Id,
}, &bson.M{
"$set": &bson.M{
"c": count,
},
})
if err != nil {
err = database.ParseError(err)
return
}
count += 1
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/node.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
func nodeUpgrade(db *database.Database) (err error) {
coll := db.Nodes()
_, err = coll.UpdateMany(db, bson.M{
"available_interfaces": bson.M{
"$exists": true,
},
"available_interfaces.0": bson.M{
"$type": "string",
},
}, []bson.M{
bson.M{
"$unset": "available_interfaces",
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"available_bridges": bson.M{
"$exists": true,
},
"available_bridges.0": bson.M{
"$type": "string",
},
}, []bson.M{
bson.M{
"$unset": "available_bridges",
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/objectid.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
func objectIdUpgrade(db *database.Database) (err error) {
nilObjectID := bson.NilObjectID
coll := db.Alerts()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Authorities()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Balancers()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"datacenter": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"datacenter": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Certificates()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"acme_secret": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"acme_secret": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Deployments()
_, err = coll.UpdateMany(db, bson.M{
"datacenter": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"datacenter": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"zone": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"zone": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"node": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"node": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"instance": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"instance": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"image": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"image": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Disks()
_, err = coll.UpdateMany(db, bson.M{
"node": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"node": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"pool": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"pool": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"instance": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"instance": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"source_instance": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"source_instance": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"deployment": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"deployment": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"image": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"image": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"restore_image": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"restore_image": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Domains()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"lock_id": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"lock_id": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Firewalls()
_, err = coll.UpdateMany(db, bson.M{
"$or": []*bson.M{
&bson.M{
"organization": nil,
},
&bson.M{
"organization": &bson.M{
"$exists": false,
},
},
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Images()
_, err = coll.UpdateMany(db, bson.M{
"deployment": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"deployment": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Instances()
_, err = coll.UpdateMany(db, bson.M{
"disk_pool": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"disk_pool": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"node": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"node": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"shape": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"shape": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
_, err = coll.UpdateMany(db, bson.M{
"deployment": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"deployment": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Plans()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Secrets()
_, err = coll.UpdateMany(db, bson.M{
"organization": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"organization": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Zones()
_, err = coll.UpdateMany(db, bson.M{
"datacenter": bson.M{
"$exists": false,
},
}, bson.M{
"$set": bson.M{
"datacenter": nilObjectID,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/roles.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
func rolesUpgrade(db *database.Database) (err error) {
coll := db.Instances()
_, err = coll.UpdateMany(db, bson.M{
"network_roles": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"roles": "$network_roles",
},
},
bson.M{
"$unset": "network_roles",
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Nodes()
_, err = coll.UpdateMany(db, bson.M{
"network_roles": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"roles": "$network_roles",
},
},
bson.M{
"$unset": "network_roles",
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Firewalls()
_, err = coll.UpdateMany(db, bson.M{
"network_roles": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"roles": "$network_roles",
},
},
bson.M{
"$unset": "network_roles",
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Authorities()
_, err = coll.UpdateMany(db, bson.M{
"network_roles": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"roles": "$network_roles",
},
},
bson.M{
"$unset": "network_roles",
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/state.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
func instStateUpgrade(db *database.Database) (err error) {
coll := db.Instances()
_, err = coll.UpdateMany(db, bson.M{
"virt_state": bson.M{
"$exists": true,
},
}, []bson.M{
bson.M{
"$set": bson.M{
"state": "$virt_state",
},
},
bson.M{
"$unset": "virt_state",
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: upgrade/upgrade.go
================================================
package upgrade
import (
"github.com/pritunl/pritunl-cloud/database"
)
func Upgrade() (err error) {
db := database.GetDatabase()
defer db.Close()
err = nodeUpgrade(db)
if err != nil {
return
}
err = rolesUpgrade(db)
if err != nil {
return
}
err = instanceUpgrade(db)
if err != nil {
return
}
err = createdUpgrade(db)
if err != nil {
return
}
err = zoneDatacenterUpgrade(db)
if err != nil {
return
}
err = instStateUpgrade(db)
if err != nil {
return
}
err = objectIdUpgrade(db)
if err != nil {
return
}
return
}
================================================
FILE: upgrade/zone_datacenter.go
================================================
package upgrade
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
type zoneUgradeDoc struct {
Id bson.ObjectID `bson:"_id"`
Node bson.ObjectID `bson:"node"`
Datacenter bson.ObjectID `bson:"datacenter"`
Zone bson.ObjectID `bson:"zone"`
}
func zoneDatacenterUpgrade(db *database.Database) (err error) {
zoneColl := db.Zones()
zoneDatacenterMap := make(map[bson.ObjectID]bson.ObjectID)
nodeMap := make(map[bson.ObjectID]*zoneUgradeDoc)
getDatacenterForZone := func(zoneID bson.ObjectID) (
bson.ObjectID, error) {
if datacenterID, ok := zoneDatacenterMap[zoneID]; ok {
return datacenterID, nil
}
zne := &zoneUgradeDoc{}
err := zoneColl.FindOne(db, bson.M{
"_id": zoneID,
}).Decode(zne)
if err != nil {
return bson.NilObjectID, database.ParseError(err)
}
zoneDatacenterMap[zoneID] = zne.Datacenter
return zne.Datacenter, nil
}
getNode := func(nodeId bson.ObjectID) (
*zoneUgradeDoc, error) {
if nde, ok := nodeMap[nodeId]; ok {
return nde, nil
}
coll := db.Nodes()
nde := &zoneUgradeDoc{}
err := coll.FindOne(db, bson.M{
"_id": nodeId,
}).Decode(nde)
if err != nil {
return nil, database.ParseError(err)
}
nodeMap[nodeId] = nde
return nde, nil
}
coll := db.Nodes()
cursor, err := coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": true},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
datacenterID, err := getDatacenterForZone(doc.Zone)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{"datacenter": datacenterID}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
coll = db.Deployments()
cursor, err = coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": true},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
datacenterID, err := getDatacenterForZone(doc.Zone)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{"datacenter": datacenterID}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
coll = db.Instances()
cursor, err = coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": true},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
datacenterID, err := getDatacenterForZone(doc.Zone)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{"datacenter": datacenterID}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
coll = db.Pools()
cursor, err = coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": true},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
datacenterID, err := getDatacenterForZone(doc.Zone)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{"datacenter": datacenterID}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
coll = db.Specs()
cursor, err = coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": true},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
datacenterID, err := getDatacenterForZone(doc.Zone)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{"datacenter": datacenterID}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
coll = db.Disks()
cursor, err = coll.Find(
db,
bson.M{
"zone": bson.M{"$exists": false},
"datacenter": bson.M{"$exists": false},
},
)
if err != nil {
return database.ParseError(err)
}
defer cursor.Close(db)
for cursor.Next(db) {
doc := &zoneUgradeDoc{}
err = cursor.Decode(doc)
if err != nil {
return database.ParseError(err)
}
nde, err := getNode(doc.Node)
if err != nil {
return err
}
_, err = coll.UpdateOne(
db,
bson.M{"_id": doc.Id},
bson.M{"$set": bson.M{
"datacenter": nde.Datacenter,
"zone": nde.Zone,
}},
)
if err != nil {
return database.ParseError(err)
}
}
err = cursor.Err()
if err != nil {
return database.ParseError(err)
}
return nil
}
================================================
FILE: usb/usb.go
================================================
package usb
import (
"fmt"
"io/ioutil"
"path"
"path/filepath"
"strings"
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
const (
syncInterval = 6 * time.Second
)
var (
syncLast time.Time
syncLock sync.Mutex
devicesCache []*Device
devicesIdMapCache map[string]*Device
devicesBusMapCache map[string]*Device
devicesBusPathMapCache map[string]*Device
)
type Device struct {
Name string `bson:"name" json:"name"`
Vendor string `bson:"vendor" json:"vendor"`
Product string `bson:"product" json:"product"`
Bus string `bson:"bus" json:"bus"`
Address string `bson:"address" json:"address"`
DeviceName string `bson:"-" json:"-"`
DevicePath string `bson:"-" json:"-"`
BusPath string `bson:"-" json:"-"`
}
func (d *Device) GetQemuId() string {
return fmt.Sprintf("usb_%s_%s_%s_%s_%d",
d.Bus,
d.Address,
d.Vendor,
d.Product,
utils.RandInt(1111, 9999),
)
}
func (d *Device) Unbind() (err error) {
unbindPath := path.Join(d.DevicePath, "driver", "unbind")
exists, err := utils.Exists(unbindPath)
if err != nil {
return
}
if exists {
err = ioutil.WriteFile(
unbindPath,
[]byte(d.DeviceName),
0644,
)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "usb: Failed to unbind '%s'", unbindPath),
}
return
}
}
return
}
func syncDevices() (err error) {
syncLock.Lock()
defer syncLock.Unlock()
devices := []*Device{}
devicesIdMap := map[string]*Device{}
devicesBusMap := map[string]*Device{}
devicesBusPathMap := map[string]*Device{}
basePath := "/sys/bus/usb/devices/"
files, err := ioutil.ReadDir(basePath)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "usb: Failed to read dir '%s'", basePath),
}
return
}
for _, file := range files {
devName := file.Name()
if strings.Contains(devName, ":") ||
strings.HasPrefix(devName, "usb") {
continue
}
devPath := filepath.Join(basePath, devName)
vendor, e := utils.ReadExists(filepath.Join(devPath, "idVendor"))
if e != nil {
err = e
return
}
if vendor == "" {
continue
}
product, e := utils.ReadExists(filepath.Join(devPath, "idProduct"))
if e != nil {
err = e
return
}
if product == "" {
continue
}
busNum, e := utils.ReadExists(filepath.Join(devPath, "busnum"))
if e != nil {
err = e
return
}
if busNum == "" {
continue
}
devNum, e := utils.ReadExists(filepath.Join(devPath, "devnum"))
if e != nil {
err = e
return
}
if devNum == "" {
continue
}
manufacturerDesc, e := utils.ReadExists(
filepath.Join(devPath, "manufacturer"))
if e != nil {
err = e
return
}
productDesc, e := utils.ReadExists(
filepath.Join(devPath, "product"))
if e != nil {
err = e
return
}
if manufacturerDesc == "" {
manufacturerDesc = "Unknown Manufacturer"
}
if productDesc == "" {
productDesc = "Unknown Product"
}
name := utils.FilterStr(strings.TrimSpace(manufacturerDesc)+
" "+strings.TrimSpace(productDesc), 256)
vendor = strings.TrimSpace(vendor)
product = strings.TrimSpace(product)
busNum = fmt.Sprintf("%03s", strings.TrimSpace(busNum))
devNum = fmt.Sprintf("%03s", strings.TrimSpace(devNum))
busPath := filepath.Join("/dev/bus/usb", busNum, devNum)
device := &Device{
Name: name,
Vendor: vendor,
Product: product,
Bus: busNum,
Address: devNum,
DeviceName: devName,
DevicePath: devPath,
BusPath: busPath,
}
devices = append(devices, device)
devicesIdMap[device.Vendor+":"+device.Product] = device
devicesBusMap[device.Bus+"-"+device.Address] = device
devicesBusPathMap[device.BusPath] = device
}
devicesCache = devices
devicesIdMapCache = devicesIdMap
devicesBusMapCache = devicesBusMap
devicesBusPathMapCache = devicesBusPathMap
syncLast = time.Now()
return
}
func GetDevices() (devices []*Device, err error) {
if time.Since(syncLast) > syncInterval {
err = syncDevices()
if err != nil {
return
}
}
syncLock.Lock()
devices = devicesCache
syncLock.Unlock()
return
}
func GetDevice(bus, address, vendor, product string) (
device *Device, err error) {
if time.Since(syncLast) > syncInterval {
err = syncDevices()
if err != nil {
return
}
}
syncLock.Lock()
if bus != "" && address != "" {
device = devicesBusMapCache[bus+"-"+address]
if device != nil && vendor != "" && product != "" {
if device.Vendor != vendor || device.Product != product {
device = nil
}
}
} else {
device = devicesIdMapCache[vendor+":"+product]
if device != nil && bus != "" && address != "" {
if device.Bus != bus || device.Address != address {
device = nil
}
}
}
syncLock.Unlock()
return
}
func GetDeviceId(vendor, product string) (device *Device, err error) {
if time.Since(syncLast) > syncInterval {
err = syncDevices()
if err != nil {
return
}
}
syncLock.Lock()
device = devicesIdMapCache[vendor+":"+product]
syncLock.Unlock()
return
}
func GetDeviceBus(bus, address string) (device *Device, err error) {
if time.Since(syncLast) > syncInterval {
err = syncDevices()
if err != nil {
return
}
}
syncLock.Lock()
device = devicesBusMapCache[bus+"-"+address]
syncLock.Unlock()
return
}
func GetDeviceBusPath(busPath string) (device *Device, err error) {
if time.Since(syncLast) > syncInterval {
err = syncDevices()
if err != nil {
return
}
}
syncLock.Lock()
device = devicesBusPathMapCache[busPath]
syncLock.Unlock()
return
}
================================================
FILE: usb/utils.go
================================================
package usb
import (
"regexp"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
)
var (
reg = regexp.MustCompile("[^a-z0-9]+")
)
func Available(db *database.Database, instId, nodeId bson.ObjectID,
device *Device) (available bool, err error) {
coll := db.Instances()
query := bson.M{
"node": nodeId,
}
if !instId.IsZero() {
query["_id"] = &bson.M{
"$ne": instId,
}
}
if device.Vendor != "" && device.Product != "" {
query["usb_devices"] = bson.M{
"$elemMatch": bson.M{
"vendor": device.Vendor,
"product": device.Product,
},
}
} else if device.Bus != "" && device.Address != "" {
query["usb_devices"] = bson.M{
"$elemMatch": bson.M{
"bus": device.Bus,
"address": device.Address,
},
}
}
count, err := coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
if count == 0 {
available = true
}
return
}
func FilterId(deviceId string) string {
deviceId = strings.ToLower(deviceId)
deviceId = reg.ReplaceAllString(deviceId, "")
if len(deviceId) != 4 {
return ""
}
return deviceId
}
func FilterAddr(addr string) string {
addr = strings.ToLower(addr)
addr = reg.ReplaceAllString(addr, "")
if len(addr) != 3 {
return ""
}
return addr
}
================================================
FILE: user/constants.go
================================================
package user
import (
"github.com/dropbox/godropbox/container/set"
)
const (
Local = "local"
Api = "api"
Azure = "azure"
AuthZero = "authzero"
Google = "google"
OneLogin = "onelogin"
Okta = "okta"
JumpCloud = "jumpcloud"
)
var (
types = set.NewSet(
Local,
Api,
Azure,
AuthZero,
Google,
OneLogin,
Okta,
JumpCloud,
)
)
================================================
FILE: user/user.go
================================================
package user
import (
"sort"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/device"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)
type User struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Type string `bson:"type" json:"type"`
Provider bson.ObjectID `bson:"provider" json:"provider"`
Username string `bson:"username" json:"username"`
Password string `bson:"password" json:"-"`
Comment string `bson:"comment" json:"comment"`
DefaultPassword string `bson:"default_password" json:"-"`
Token string `bson:"token" json:"token"`
Secret string `bson:"secret" json:"secret"`
Theme string `bson:"theme" json:"-"`
EditorTheme string `bson:"editor_theme" json:"-"`
LastActive time.Time `bson:"last_active" json:"last_active"`
LastSync time.Time `bson:"last_sync" json:"last_sync"`
Roles []string `bson:"roles" json:"roles"`
Administrator string `bson:"administrator" json:"administrator"`
Disabled bool `bson:"disabled" json:"disabled"`
ActiveUntil time.Time `bson:"active_until" json:"active_until"`
Permissions []string `bson:"permissions" json:"permissions"`
OracleLicense bool `bson:"oracle_licese" json:"oracle_license"`
WanCredentials []webauthn.Credential `bson:"-" json:"-"`
}
func (u *User) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
if u.Roles == nil {
u.Roles = []string{}
}
if u.Permissions == nil {
u.Permissions = []string{}
}
if !types.Contains(u.Type) {
errData = &errortypes.ErrorData{
Error: "user_type_invalid",
Message: "User type is not valid",
}
return
}
if u.Username == "" {
errData = &errortypes.ErrorData{
Error: "user_username_invalid",
Message: "User username is not valid",
}
return
}
if u.Type == Local && u.Password == "" {
errData = &errortypes.ErrorData{
Error: "user_password_missing",
Message: "User password is not set",
}
return
}
u.Format()
return
}
func (u *User) Format() {
if u.Type == Local {
u.Username = strings.ToLower(u.Username)
}
roles := []string{}
rolesSet := set.NewSet()
for _, role := range u.Roles {
rolesSet.Add(role)
}
for role := range rolesSet.Iter() {
roles = append(roles, role.(string))
}
sort.Strings(roles)
u.Roles = roles
}
func (u *User) SuperExists(db *database.Database) (
errData *errortypes.ErrorData, err error) {
if u.Administrator != "super" && !u.Id.IsZero() {
exists, e := hasSuperSkip(db, u.Id)
if e != nil {
err = e
return
}
if !exists {
errData = &errortypes.ErrorData{
Error: "user_missing_super",
Message: "Missing super administrator",
}
return
}
}
return
}
func (u *User) Commit(db *database.Database) (err error) {
coll := db.Users()
err = coll.Commit(u.Id, u)
if err != nil {
return
}
return
}
func (u *User) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Users()
err = coll.CommitFields(u.Id, u, fields)
if err != nil {
return
}
return
}
func (u *User) Insert(db *database.Database) (err error) {
coll := db.Users()
if !u.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("user: User already exists"),
}
return
}
_, err = coll.InsertOne(db, u)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (u *User) Upsert(db *database.Database) (err error) {
coll := db.Users()
opts := options.FindOneAndUpdate().
SetUpsert(true).
SetReturnDocument(options.After)
err = coll.FindOneAndUpdate(
db,
&bson.M{
"type": u.Type,
"username": u.Username,
},
&bson.M{
"$setOnInsert": u,
},
opts,
).Decode(u)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (u *User) RolesMatch(roles []string) bool {
usrRoles := set.NewSet()
for _, role := range u.Roles {
usrRoles.Add(role)
}
for _, role := range roles {
if usrRoles.Contains(role) {
return true
}
}
return false
}
func (u *User) RolesMerge(roles []string) bool {
newRoles := set.NewSet()
curRoles := set.NewSet()
for _, role := range roles {
newRoles.Add(role)
}
for _, role := range u.Roles {
newRoles.Add(role)
curRoles.Add(role)
}
if !curRoles.IsEqual(newRoles) {
rls := []string{}
for role := range newRoles.Iter() {
rls = append(rls, role.(string))
}
u.Roles = rls
return true
}
return false
}
func (u *User) RolesOverwrite(roles []string) bool {
newRoles := set.NewSet()
curRoles := set.NewSet()
for _, role := range roles {
newRoles.Add(role)
}
for _, role := range u.Roles {
curRoles.Add(role)
}
if !curRoles.IsEqual(newRoles) {
u.Roles = roles
return true
}
return false
}
func (u *User) SetPassword(password string) (err error) {
if u.Type != Local {
err = &errortypes.UnknownError{
errors.New("user: User type cannot store password"),
}
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(password), 10)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "user: Failed to hash password"),
}
return
}
u.Password = string(hash)
u.DefaultPassword = ""
return
}
func (u *User) GenerateDefaultPassword() (err error) {
passwd, err := utils.RandStr(12)
if err != nil {
return
}
err = u.SetPassword(passwd)
if err != nil {
return
}
u.DefaultPassword = passwd
return
}
func (u *User) CheckPassword(password string) bool {
if u.Type != Local || u.Password == "" {
return false
}
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
if err != nil {
return false
}
return true
}
func (u *User) GenerateToken() (err error) {
u.Token, err = utils.RandStr(48)
if err != nil {
return
}
u.Secret, err = utils.RandStr(48)
if err != nil {
return
}
return
}
func (u *User) GetDevices(db *database.Database) (
devices []*device.Device, err error) {
devices, err = device.GetAll(db, u.Id)
if err != nil {
return
}
return
}
func (u *User) LoadWebAuthnDevices(db *database.Database) (
devices []*device.Device, hasU2f bool, err error) {
devices, err = device.GetAll(db, u.Id)
if err != nil {
return
}
wanCredentials := []webauthn.Credential{}
for _, devc := range devices {
switch devc.Type {
case device.WebAuthn:
break
case device.U2f:
hasU2f = true
break
default:
continue
}
wanCred, e := devc.UnmarshalWebauthn()
if e != nil {
err = e
return
}
wanCredentials = append(wanCredentials, wanCred)
}
u.WanCredentials = wanCredentials
return
}
func (u *User) WebAuthnID() []byte {
return u.Id[:]
}
func (u *User) WebAuthnName() string {
return u.Username
}
func (u *User) WebAuthnDisplayName() string {
return u.Username
}
func (u *User) WebAuthnIcon() string {
return ""
}
func (u *User) WebAuthnCredentials() []webauthn.Credential {
return u.WanCredentials
}
func init() {
module := requires.New("user")
module.After("settings")
module.Handler = func() (err error) {
db := database.GetDatabase()
defer db.Close()
coll := db.Users()
cursor, err := coll.Find(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
usr := &User{}
err = cursor.Decode(usr)
if err != nil {
err = database.ParseError(err)
return
}
newUsername := strings.ToLower(usr.Username)
if usr.Username != newUsername {
err = coll.UpdateId(usr.Id, &bson.M{
"$set": &bson.M{
"username": newUsername,
},
})
if err != nil {
return
}
}
}
count, err := Count(db)
if err != nil {
return
}
if count == 0 {
logrus.Info("user: Creating default super user")
usr := User{
Type: Local,
Username: "pritunl",
Administrator: "super",
Roles: []string{"org"},
}
err = usr.GenerateDefaultPassword()
if err != nil {
return
}
_, err = usr.Validate(db)
if err != nil {
return
}
err = usr.Insert(db)
if err != nil {
return
}
}
return
}
}
================================================
FILE: user/utils.go
================================================
package user
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
func Get(db *database.Database, userId bson.ObjectID) (
usr *User, err error) {
coll := db.Users()
usr = &User{}
err = coll.FindOneId(userId, usr)
if err != nil {
return
}
return
}
func GetUpdate(db *database.Database, userId bson.ObjectID) (
usr *User, err error) {
coll := db.Users()
usr = &User{}
timestamp := time.Now()
err = coll.FindOneAndUpdate(
db,
&bson.M{
"_id": userId,
},
&bson.M{
"$set": &bson.M{
"last_active": timestamp,
},
},
).Decode(usr)
if err != nil {
err = database.ParseError(err)
return
}
usr.LastActive = timestamp
return
}
func GetTokenUpdate(db *database.Database, token string) (
usr *User, err error) {
coll := db.Users()
usr = &User{}
timestamp := time.Now()
err = coll.FindOneAndUpdate(
db,
&bson.M{
"token": token,
},
&bson.M{
"$set": &bson.M{
"last_active": timestamp,
},
},
).Decode(usr)
if err != nil {
err = database.ParseError(err)
return
}
usr.LastActive = timestamp
return
}
func GetUsername(db *database.Database, typ, username string) (
usr *User, err error) {
coll := db.Users()
usr = &User{}
if username == "" {
err = &errortypes.NotFoundError{
errors.New("user: Username empty"),
}
return
}
err = coll.FindOne(db, &bson.M{
"type": typ,
"username": username,
}).Decode(usr)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAll(db *database.Database, query *bson.M, page, pageCount int64) (
users []*User, count int64, err error) {
coll := db.Users()
users = []*User{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
opts := options.Find().
SetSort(bson.D{{"username", 1}})
if pageCount != 0 {
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
opts.SetSkip(skip).SetLimit(pageCount)
}
cursor, err := coll.Find(
db,
query,
opts,
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
usr := &User{}
err = cursor.Decode(usr)
if err != nil {
err = database.ParseError(err)
return
}
users = append(users, usr)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Remove(db *database.Database, userIds []bson.ObjectID) (
errData *errortypes.ErrorData, err error) {
coll := db.Users()
opts := options.Count().
SetLimit(1)
count, err := coll.CountDocuments(
db,
&bson.M{
"_id": &bson.M{
"$nin": userIds,
},
"administrator": "super",
},
opts,
)
if err != nil {
err = database.ParseError(err)
return
}
if count == 0 {
errData = &errortypes.ErrorData{
Error: "user_remove_super",
Message: "Cannot remove all super administrators",
}
return
}
coll = db.Sessions()
_, err = coll.DeleteMany(db, &bson.M{
"user": &bson.M{
"$in": userIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Users()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": userIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func Count(db *database.Database) (count int64, err error) {
coll := db.Users()
count, err = coll.CountDocuments(db, &bson.M{})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func hasSuperSkip(db *database.Database, skipId bson.ObjectID) (
exists bool, err error) {
coll := db.Users()
opts := options.Count().
SetLimit(1)
count, err := coll.CountDocuments(
db,
&bson.M{
"_id": &bson.M{
"$ne": skipId,
},
"administrator": "super",
},
opts,
)
if err != nil {
err = database.ParseError(err)
return
}
if count > 0 {
exists = true
}
return
}
================================================
FILE: useragent/useragent.go
================================================
package useragent
import (
"net/http"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/geo"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/sirupsen/logrus"
"github.com/ua-parser/uap-go/uaparser"
)
var (
parser = uaparser.NewFromSaved()
)
const (
Linux = "linux" // Linux = Debian + Linux + Ubuntu
MacOs1010 = "macos_1010" // macOS 10.10 = Mac OS X (10/10)
MacOs1011 = "macos_1011" // macOS 10.11 = Mac OS X (10/11)
MacOs1012 = "macos_1012" // macOS 10.12 = Mac OS X (10/12)
MacOs1013 = "macos_1013" // macOS 10.13 = Mac OS X (10/13)
MacOs1014 = "macos_1014" // macOS 10.14 = Mac OS X (10/14)
MacOs1015 = "macos_1015" // macOS 10.15 = Mac OS X (10/15)
MacOs11 = "macos_11" // macOS 11 = Mac OS X (11)
MacOs12 = "macos_12" // macOS 12 = Mac OS X (12)
MacOs13 = "macos_13" // macOS 13 = Mac OS X (13)
MacOs14 = "macos_14" // macOS 14 = Mac OS X (14)
MacOs15 = "macos_15" // macOS 15 = Mac OS X (15)
MacOs16 = "macos_16" // macOS 16 = Mac OS X (16)
WindowsXp = "windows_xp" // Windows XP = Windows XP
Windows7 = "windows_7" // Windows 7 = Windows 7
WindowsVista = "windows_vista" // Windows Vista = Windows Vista
Windows8 = "windows_8" // Windows 8 = Windows 8 + Windows 8.1 + Windows RT 8.1
Windows10 = "windows_10" // Windows 10 = Windows 10
Windows11 = "windows_11" // Windows 11 = Windows 11
ChromeOs = "chrome_os" // Chrome OS = Chrome OS
Ios8 = "ios_8" // iOS 8 = iOS (8/x)
Ios9 = "ios_9" // iOS 9 = iOS (9/x)
Ios10 = "ios_10" // iOS 10 = iOS (10/x)
Ios11 = "ios_11" // iOS 11 = iOS (11/x)
Ios12 = "ios_12" // iOS 12 = iOS (12/x)
Ios13 = "ios_13" // iOS 13 = iOS (13/x)
Ios14 = "ios_14" // iOS 14 = iOS (14/x)
Ios15 = "ios_15" // iOS 15 = iOS (15/x)
Ios16 = "ios_16" // iOS 16 = iOS (16/x)
Ios17 = "ios_17" // iOS 17 = iOS (17/x)
Ios18 = "ios_18" // iOS 18 = iOS (18/x)
Ios19 = "ios_19" // iOS 19 = iOS (19/x)
Ios20 = "ios_20" // iOS 20 = iOS (20/x)
Android4 = "android_4" // Android KitKat 4.4 = Android (4/4)
Android5 = "android_5" // Android Lollipop 5.0 = Android (5/x)
Android6 = "android_6" // Android Marshmallow 6.0 = Android (6/x)
Android7 = "android_7" // Android Nougat 7.0 = Android (7/x)
Android8 = "android_8" // Android Oreo 8.0 = Android (8/x)
Android9 = "android_9" // Android Pie 9.0 = Android (9/x)
Android10 = "android_10" // Android 10.0 = Android (10/x)
Android11 = "android_11" // Android 11.0 = Android (11/x)
Android12 = "android_12" // Android 12.0 = Android (12/x)
Android13 = "android_13" // Android 13.0 = Android (13/x)
Android14 = "android_14" // Android 14.0 = Android (14/x)
Android15 = "android_15" // Android 15.0 = Android (15/x)
Android16 = "android_16" // Android 16.0 = Android (16/x)
Blackberry10 = "blackberry_10" // Blackerry 10 = BlackBerry OS (10/x)
WindowsPhone = "windows_phone" // Windows Phone = Windows Phone
FirefoxOs = "firefox_os" // Firefox OS = Firefox OS
Kindle = "kindle" // Kindle = Kindle
)
const (
Chrome = "chrome" // Chrome = Chrome + Chromium
ChromeMobile = "chrome_mobile" // Chrome Mobile = Chrome Mobile + Chrome Mobile iOS + Chrome Mobile WebView
Safari = "safari" // Safari = Safari
SafariMobile = "safari_mobile" // Safari Mobile = Mobile Safari + Mobile Safari UI/WKWebView
Firefox = "firefox" // Firefox = Firefox + Firefox Beta
FirefoxMobile = "firefox_mobile" // Firefox Mobile = Firefox Mobile + Firefox iOS
Edge = "edge" // Microsoft Edge = Edge
InternetExplorer = "internet_explorer" // Internet Explorer = IE
InternetExplorerMobile = "internet_explorer_mobile" // Internet Explorer Mobile = IE Mobile
Opera = "opera" // Opera = Opera
OperaMobile = "opera_mobile" // Opera Mobile = Opera Mini + Opera Mobile + Opera Tablet + Opera Coast
)
type Agent struct {
OperatingSystem string `bson:"operating_system" json:"operating_system"`
Browser string `bson:"browser" json:"browser"`
Ip string `bson:"ip" json:"ip"`
Isp string `bson:"isp" json:"isp"`
Continent string `bson:"continent" json:"continent"`
ContinentCode string `bson:"continent_code" json:"continent_code"`
Country string `bson:"country" json:"country"`
CountryCode string `bson:"country_code" json:"country_code"`
Region string `bson:"region" json:"region"`
RegionCode string `bson:"region_code" json:"region_code"`
City string `bson:"city" json:"city"`
Latitude float64 `bson:"latitude" json:"latitude"`
Longitude float64 `bson:"longitude" json:"longitude"`
}
func Parse(db *database.Database, r *http.Request) (agnt *Agent, err error) {
if settings.System.Demo {
return
}
client := parser.Parse(r.UserAgent())
ip := node.Self.GetRemoteAddr(r)
ge, err := geo.Get(db, ip)
if err != nil {
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("agent: Failed to get geo IP information")
err = nil
return
}
agnt = &Agent{
Ip: ip,
Isp: ge.Isp,
Continent: ge.Continent,
ContinentCode: ge.ContinentCode,
Country: ge.Country,
CountryCode: ge.CountryCode,
Region: ge.Region,
RegionCode: ge.RegionCode,
City: ge.City,
Longitude: ge.Longitude,
Latitude: ge.Latitude,
}
switch client.Os.Family {
case "Android":
switch client.Os.Major {
case "4":
if client.Os.Minor == "4" {
agnt.OperatingSystem = Android4
break
}
break
case "5":
agnt.OperatingSystem = Android5
break
case "6":
agnt.OperatingSystem = Android6
break
case "7":
agnt.OperatingSystem = Android7
break
case "8":
agnt.OperatingSystem = Android8
break
case "9":
agnt.OperatingSystem = Android9
break
case "10":
agnt.OperatingSystem = Android10
break
case "11":
agnt.OperatingSystem = Android11
break
case "12":
agnt.OperatingSystem = Android12
break
case "13":
agnt.OperatingSystem = Android13
break
case "14":
agnt.OperatingSystem = Android14
break
case "15":
agnt.OperatingSystem = Android15
break
case "16":
agnt.OperatingSystem = Android16
break
}
break
case "BlackBerry OS":
if client.Os.Major == "10" {
agnt.OperatingSystem = Blackberry10
break
}
break
case "Firefox OS":
agnt.OperatingSystem = FirefoxOs
break
case "iOS":
switch client.Os.Major {
case "8":
agnt.OperatingSystem = Ios8
break
case "9":
agnt.OperatingSystem = Ios9
break
case "10":
agnt.OperatingSystem = Ios10
break
case "11":
agnt.OperatingSystem = Ios11
break
case "12":
agnt.OperatingSystem = Ios12
break
case "13":
agnt.OperatingSystem = Ios13
break
case "14":
agnt.OperatingSystem = Ios14
break
case "15":
agnt.OperatingSystem = Ios15
break
case "16":
agnt.OperatingSystem = Ios16
break
case "17":
agnt.OperatingSystem = Ios17
break
case "18":
agnt.OperatingSystem = Ios18
break
case "19":
agnt.OperatingSystem = Ios19
break
case "20":
agnt.OperatingSystem = Ios20
break
}
break
case "Kindle":
agnt.OperatingSystem = Kindle
break
case "Mac OS X":
switch client.Os.Major {
case "10":
switch client.Os.Minor {
case "10":
agnt.OperatingSystem = MacOs1010
break
case "11":
agnt.OperatingSystem = MacOs1011
break
case "12":
agnt.OperatingSystem = MacOs1012
break
case "13":
agnt.OperatingSystem = MacOs1013
break
case "14":
agnt.OperatingSystem = MacOs1014
break
case "15":
agnt.OperatingSystem = MacOs1015
break
}
break
case "11":
agnt.OperatingSystem = MacOs11
break
case "12":
agnt.OperatingSystem = MacOs12
break
case "13":
agnt.OperatingSystem = MacOs13
break
case "14":
agnt.OperatingSystem = MacOs14
break
case "15":
agnt.OperatingSystem = MacOs15
break
case "16":
agnt.OperatingSystem = MacOs16
break
}
break
case "Windows Phone":
agnt.OperatingSystem = WindowsPhone
break
case "Windows XP":
agnt.OperatingSystem = WindowsXp
break
case "Windows 7":
agnt.OperatingSystem = Windows7
break
case "Windows Vista":
agnt.OperatingSystem = WindowsVista
break
case "Windows 8", "Windows 8.1", "Windows RT 8.1":
agnt.OperatingSystem = Windows8
break
case "Windows 10":
agnt.OperatingSystem = Windows10
break
case "Windows 11":
agnt.OperatingSystem = Windows11
break
case "Chrome OS":
agnt.OperatingSystem = ChromeOs
break
case "Linux", "Debian", "Ubuntu":
agnt.OperatingSystem = Linux
break
}
switch client.UserAgent.Family {
case "Chrome", "Chromium":
agnt.Browser = Chrome
break
case "Chrome Mobile", "Chrome Mobile iOS", "Chrome Mobile WebView":
agnt.Browser = ChromeMobile
break
case "Safari":
agnt.Browser = Safari
break
case "Mobile Safari", "Mobile Safari UI/WKWebView":
agnt.Browser = SafariMobile
break
case "Firefox", "Firefox Beta":
agnt.Browser = Firefox
break
case "Firefox Mobile", "Firefox iOS":
agnt.Browser = FirefoxMobile
break
case "Edge":
agnt.Browser = Edge
break
case "IE":
agnt.Browser = InternetExplorer
break
case "IE Mobile":
agnt.Browser = InternetExplorerMobile
break
case "Opera":
agnt.Browser = Opera
break
case "Opera Mini", "Opera Mobile", "Opera Tablet", "Opera Coast":
agnt.Browser = OperaMobile
break
}
return
}
func (a *Agent) Diff(agnt *Agent) bool {
if a.OperatingSystem != agnt.OperatingSystem ||
a.Browser != agnt.Browser ||
a.Ip != agnt.Ip ||
a.Isp != agnt.Isp ||
a.Continent != agnt.Continent ||
a.ContinentCode != agnt.ContinentCode ||
a.Country != agnt.Country ||
a.CountryCode != agnt.CountryCode ||
a.Region != agnt.Region ||
a.RegionCode != agnt.RegionCode ||
a.City != agnt.City ||
a.Longitude != agnt.Longitude ||
a.Latitude != agnt.Latitude {
return true
}
return false
}
================================================
FILE: utils/crypto.go
================================================
package utils
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/gob"
"encoding/pem"
"fmt"
"hash/crc32"
"math"
"math/big"
mathrand "math/rand"
"regexp"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var (
randRe = regexp.MustCompile("[^a-zA-Z0-9]+")
randPasswdRe = regexp.MustCompile(
"[^23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ]+")
)
func CrcHash(input interface{}) (sum uint32, err error) {
hash := crc32.NewIEEE()
enc := gob.NewEncoder(hash)
err = enc.Encode(input)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Failed to encode crc input"),
}
return
}
sum = hash.Sum32()
return
}
func RandStr(n int) (str string, err error) {
for i := 0; i < 10; i++ {
input, e := RandBytes(int(math.Ceil(float64(n) * 1.25)))
if e != nil {
err = e
return
}
output := base64.RawStdEncoding.EncodeToString(input)
output = randRe.ReplaceAllString(output, "")
if len(output) < n {
continue
}
str = output[:n]
break
}
if str == "" {
err = &errortypes.UnknownError{
errors.Wrap(err, "utils: Random generate error"),
}
return
}
return
}
func RandPasswd(n int) (str string, err error) {
for i := 0; i < 10; i++ {
input, e := RandBytes(n * 2)
if e != nil {
err = e
return
}
output := base64.RawStdEncoding.EncodeToString(input)
output = randPasswdRe.ReplaceAllString(output, "")
if len(output) < n {
continue
}
str = output[:n]
break
}
if str == "" {
err = &errortypes.UnknownError{
errors.Wrap(err, "utils: Random generate error"),
}
return
}
return
}
func RandBytes(size int) (bytes []byte, err error) {
bytes = make([]byte, size)
_, err = rand.Read(bytes)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "utils: Random read error"),
}
return
}
return
}
func RandMacAddr() (addr string, err error) {
bytes := make([]byte, 6)
_, err = rand.Read(bytes)
if err != nil {
err = &errortypes.UnknownError{
errors.Wrap(err, "utils: Random read error"),
}
return
}
addr = strings.ToUpper(fmt.Sprintf("%x", bytes))
return
}
func GenerateRsaKey() (encodedPriv, encodedPub []byte, err error) {
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to generate rsa key"),
}
return
}
blockPriv := &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
}
encodedPriv = pem.EncodeToMemory(blockPriv)
bytesPub, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Failed to marshal rsa public key"),
}
return
}
blockPub := &pem.Block{
Type: "PUBLIC KEY",
Bytes: bytesPub,
}
encodedPub = pem.EncodeToMemory(blockPub)
return
}
func RandObjectId() (oid bson.ObjectID, err error) {
rid, err := RandBytes(12)
if err != nil {
return
}
copy(oid[:], rid)
return
}
func RandInt(min, max int) int {
return mathrand.Intn(max-min+1) + min
}
func init() {
n, err := rand.Int(rand.Reader, big.NewInt(9223372036854775806))
if err != nil {
panic(err)
}
mathrand.Seed(n.Int64())
}
================================================
FILE: utils/dns.go
================================================
package utils
import (
"context"
"fmt"
"net"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func DnsLookup(server, host string) (addrs []string, err error) {
serverIp := net.ParseIP(server)
if serverIp == nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Invalid DNS server address"),
}
return
}
if serverIp.To4() == nil {
server = fmt.Sprintf("[%s]:53", serverIp.String())
} else {
server = fmt.Sprintf("%s:53", serverIp.String())
}
resolver := &net.Resolver{
PreferGo: true,
Dial: func(ctx context.Context, network,
address string) (net.Conn, error) {
dialer := net.Dialer{
Timeout: 3 * time.Second,
}
return dialer.DialContext(ctx, network, server)
},
}
addrs, err = resolver.LookupHost(context.Background(), host)
if err != nil {
err = &errortypes.RequestError{
errors.Wrap(err, "utils: DNS lookup failed"),
}
return
}
if addrs == nil {
addrs = []string{}
}
return
}
================================================
FILE: utils/files.go
================================================
package utils
import (
"bufio"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var invalidPaths = set.NewSet("/", "", ".", "./")
const pathSafeLimit = 256
var pathSafeChars = set.NewSet(
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'-',
'_',
'.',
'+',
'=',
'@',
'/',
)
func FilterPath(pth string) string {
if len(pth) > pathSafeLimit {
pth = pth[:pathSafeLimit]
}
cleaned := ""
for _, c := range pth {
if pathSafeChars.Contains(c) {
cleaned += string(c)
}
}
cleaned = filepath.Clean(cleaned)
cleaned, err := filepath.Abs(cleaned)
if err != nil {
return ""
}
cleaned = filepath.FromSlash(cleaned)
cleaned = strings.ReplaceAll(cleaned, "..", "")
return cleaned
}
func FilterRelPath(pth string) string {
if len(pth) > pathSafeLimit {
pth = pth[:pathSafeLimit]
}
cleaned := ""
for _, c := range pth {
if pathSafeChars.Contains(c) {
cleaned += string(c)
}
}
cleaned = filepath.Clean(cleaned)
cleaned = filepath.FromSlash(cleaned)
cleaned = strings.ReplaceAll(cleaned, "..", "")
return cleaned
}
func Chmod(pth string, mode os.FileMode) (err error) {
err = os.Chmod(pth, mode)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to chmod %s", pth),
}
return
}
return
}
func Exists(pth string) (exists bool, err error) {
_, err = os.Stat(pth)
if err == nil {
exists = true
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsDir(pth string) (exists bool, err error) {
stat, err := os.Stat(pth)
if err == nil {
exists = stat.IsDir()
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsFile(pth string) (exists bool, err error) {
stat, err := os.Stat(pth)
if err == nil {
exists = !stat.IsDir()
return
}
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to stat %s", pth),
}
return
}
func ExistsMkdir(pth string, perm os.FileMode) (err error) {
exists, err := ExistsDir(pth)
if err != nil {
return
}
if !exists {
err = os.MkdirAll(pth, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to mkdir %s", pth),
}
return
}
}
return
}
func ExistsRemove(pth string) (err error) {
exists, err := Exists(pth)
if err != nil {
return
}
if exists {
err = os.RemoveAll(pth)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to rm %s", pth),
}
return
}
}
return
}
func Remove(path string) (err error) {
if invalidPaths.Contains(path) {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Invalid remove path '%s'", path),
}
return
}
err = os.Remove(path)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to remove '%s'", path),
}
return
}
return
}
func RemoveAll(path string) (err error) {
if invalidPaths.Contains(path) {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Invalid remove path '%s'", path),
}
return
}
err = os.RemoveAll(path)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to remove '%s'", path),
}
return
}
return
}
func RemoveWildcard(matchPath string) (n int, err error) {
matches, err := filepath.Glob(matchPath)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Error matching path '%s'", matchPath),
}
return
}
if len(matches) == 0 {
return
}
delErrors := []string{}
for _, pth := range matches {
fileInfo, err := os.Stat(pth)
if err != nil {
delErrors = append(delErrors, fmt.Sprintf("%s: %v", pth, err))
continue
}
if fileInfo.IsDir() {
continue
}
err = os.Remove(pth)
if err != nil {
delErrors = append(delErrors, fmt.Sprintf("%s: %v", pth, err))
} else {
n += 1
}
}
if len(delErrors) > 0 {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Delete errors '%s'",
strings.Join(delErrors, ",")),
}
return
}
return
}
func ContainsDir(pth string) (hasDir bool, err error) {
exists, err := ExistsDir(pth)
if !exists {
return
}
entries, err := ioutil.ReadDir(pth)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "queue: Failed to read dir %s", pth),
}
return
}
for _, entry := range entries {
if entry.IsDir() {
hasDir = true
return
}
}
return
}
func Open(path string, perm os.FileMode) (file *os.File, err error) {
file, err = os.OpenFile(path, os.O_RDWR|os.O_TRUNC, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to open '%s'", path),
}
return
}
return
}
func Read(path string) (data string, err error) {
dataByt, err := ioutil.ReadFile(path)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", path),
}
return
}
data = string(dataByt)
return
}
func ReadExists(path string) (data string, err error) {
dataByt, err := ioutil.ReadFile(path)
if err != nil {
if os.IsNotExist(err) {
err = nil
return
}
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", path),
}
return
}
data = string(dataByt)
return
}
func ReadLines(path string) (lines []string, err error) {
file, err := os.Open(path)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to open '%s'", path),
}
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", path),
}
return
}
}()
lines = []string{}
reader := bufio.NewReader(file)
for {
line, e := reader.ReadString('\n')
if e != nil {
break
}
lines = append(lines, strings.Trim(line, "\n"))
}
return
}
func Write(path string, data string, perm os.FileMode) (err error) {
file, err := Open(path, perm)
if err != nil {
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write '%s'", path),
}
return
}
}()
_, err = file.WriteString(data)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write to file '%s'", path),
}
return
}
return
}
func Create(path string, perm os.FileMode) (file *os.File, err error) {
file, err = os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, perm)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to create '%s'", path),
}
return
}
return
}
func CreateWrite(path string, data string, perm os.FileMode) (err error) {
file, err := Create(path, perm)
if err != nil {
return
}
defer func() {
err = file.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write '%s'", path),
}
return
}
}()
_, err = file.WriteString(data)
if err != nil {
err = &errortypes.WriteError{
errors.Wrapf(err, "utils: Failed to write to file '%s'", path),
}
return
}
return
}
func FileSha256(pth string) (hash string, err error) {
file, err := os.Open(pth)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", pth),
}
return
}
defer file.Close()
hasher := sha256.New()
_, err = io.Copy(hasher, file)
if err != nil {
err = &errortypes.ReadError{
errors.Wrapf(err, "utils: Failed to read '%s'", pth),
}
return
}
hash = fmt.Sprintf("%x", hasher.Sum(nil))
return
}
================================================
FILE: utils/filter.go
================================================
package utils
import (
"strings"
"github.com/dropbox/godropbox/container/set"
)
const nameSafeLimit = 128
var nameSafeChar = set.NewSet(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'-', '.',
)
var nameCmdSafeChar = set.NewSet(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'-', '.',
)
var unitSafeChar = set.NewSet(
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
':', '-', '_', '.', '\\', '@',
)
func FilterName(s string) string {
if len(s) == 0 {
return ""
}
if s == "self" {
s = "invalid-name"
}
if len(s) > nameSafeLimit {
s = s[:nameSafeLimit]
}
var ns strings.Builder
for _, c := range s {
if nameSafeChar.Contains(c) {
ns.WriteString(string(c))
}
}
return ns.String()
}
func FilterNameCmd(s string) string {
if len(s) == 0 {
return ""
}
if s == "self" {
s = "invalid-name"
}
if len(s) > nameSafeLimit {
s = s[:nameSafeLimit]
}
var ns strings.Builder
for _, c := range s {
if nameCmdSafeChar.Contains(c) {
ns.WriteString(string(c))
}
}
return strings.ToLower(ns.String())
}
func FilterUnit(s string) string {
if len(s) == 0 {
return ""
}
if len(s) > nameSafeLimit {
s = s[:nameSafeLimit]
}
var ns strings.Builder
for _, c := range s {
if unitSafeChar.Contains(c) {
ns.WriteString(string(c))
}
}
return ns.String()
}
func FilterDomain(s string) string {
s = FilterName(strings.ToLower(s))
s = strings.TrimPrefix(s, ".")
s = strings.TrimSuffix(s, ".")
return s
}
================================================
FILE: utils/limiter.go
================================================
package utils
import (
"sync"
"time"
)
type Limiter struct {
counter int
limit int
lock sync.Mutex
}
func (l *Limiter) Acquire() (acquired bool) {
l.lock.Lock()
if l.counter < l.limit {
l.counter += 1
acquired = true
}
l.lock.Unlock()
return
}
func (l *Limiter) Release() {
l.lock.Lock()
l.counter -= 1
if l.counter < 0 {
panic("limiter: Counter below zero")
}
l.lock.Unlock()
}
func NewLimiter(limit int) *Limiter {
return &Limiter{
counter: 0,
limit: limit,
lock: sync.Mutex{},
}
}
type TimeLimiter struct {
lastRelease time.Time
duration time.Duration
acquired bool
lock sync.Mutex
}
func (l *TimeLimiter) SetDuration(duration time.Duration) {
l.lock.Lock()
l.duration = duration
l.lock.Unlock()
}
func (l *TimeLimiter) Acquire() (acquired bool) {
l.lock.Lock()
defer l.lock.Unlock()
if l.acquired {
return false
}
if time.Since(l.lastRelease) >= l.duration {
l.acquired = true
acquired = true
}
return
}
func (l *TimeLimiter) Release() {
l.lock.Lock()
defer l.lock.Unlock()
if !l.acquired {
panic("limiter: Release called without acquire")
}
l.lastRelease = time.Now()
l.acquired = false
}
func NewTimeLimiter(duration time.Duration) *TimeLimiter {
return &TimeLimiter{
lastRelease: time.Time{},
duration: duration,
acquired: false,
lock: sync.Mutex{},
}
}
================================================
FILE: utils/math.go
================================================
package utils
import (
"math"
)
func Max(x, y int) int {
if x > y {
return x
}
return y
}
func Min(x, y int) int {
if x < y {
return x
}
return y
}
func Max64(x, y int64) int64 {
if x > y {
return x
}
return y
}
func Min64(x, y int64) int64 {
if x < y {
return x
}
return y
}
func ToFixed(x float64, p int) float64 {
y := math.Pow(10, float64(p))
return float64(int(x*y+math.Copysign(0.5, x*y))) / y
}
================================================
FILE: utils/misc.go
================================================
package utils
import (
"container/list"
"io/ioutil"
"os/exec"
"regexp"
"runtime/debug"
"strconv"
"strings"
"time"
"unicode"
"github.com/dropbox/godropbox/container/set"
"github.com/sirupsen/logrus"
)
var isSystemd *bool
var safeChars = set.NewSet(
'a',
'b',
'c',
'd',
'e',
'f',
'g',
'h',
'i',
'j',
'k',
'l',
'm',
'n',
'o',
'p',
'q',
'r',
's',
't',
'u',
'v',
'w',
'x',
'y',
'z',
'A',
'B',
'C',
'D',
'E',
'F',
'G',
'H',
'I',
'J',
'K',
'L',
'M',
'N',
'O',
'P',
'Q',
'R',
'S',
'T',
'U',
'V',
'W',
'X',
'Y',
'Z',
'0',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'-',
'+',
'=',
'_',
'/',
',',
'.',
'~',
'@',
'#',
'!',
'&',
' ',
)
func FilterStr(s string, n int) string {
if len(s) == 0 {
return ""
}
if len(s) > n {
s = s[:n]
}
var ns strings.Builder
for _, c := range s {
if safeChars.Contains(c) {
ns.WriteString(string(c))
}
}
return ns.String()
}
func SinceAbs(t time.Time) (s time.Duration) {
s = time.Since(t)
if s < 0 {
s = s * -1
}
return
}
func PointerBool(x bool) *bool {
return &x
}
func PointerInt(x int) *int {
return &x
}
func PointerString(x string) *string {
return &x
}
func Int8Str(arr []int8) string {
b := make([]byte, 0, len(arr))
for _, v := range arr {
if v == 0x00 {
break
}
b = append(b, byte(v))
}
return string(b)
}
func HasPreSuf(src, pre, suf string) bool {
return strings.HasPrefix(src, pre) && strings.HasSuffix(src, suf)
}
func IsSystemd() bool {
if isSystemd != nil {
return *isSystemd
}
data, err := ioutil.ReadFile("/proc/1/cmdline")
if err == nil {
parts := strings.Split(string(data), "\x00")
if len(parts) > 0 && strings.Contains(
strings.ToLower(parts[0]), "systemd") {
isSysd := true
isSystemd = &isSysd
return true
}
}
data, err = ioutil.ReadFile("/proc/1/comm")
if err == nil {
if strings.Contains(strings.ToLower(string(data)), "systemd") {
isSysd := true
isSystemd = &isSysd
return true
}
}
cmd := exec.Command("ps", "-p", "1", "-o", "comm=")
output, err := cmd.Output()
if err == nil {
if strings.Contains(strings.ToLower(string(output)), "systemd") {
isSysd := true
isSystemd = &isSysd
return true
}
}
isSysd := false
isSystemd = &isSysd
return false
}
func CompareStringSlices(a, b []string) bool {
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
func CompareStringSlicesUnsorted(a, b []string) bool {
aSet := set.NewSet()
for _, val := range a {
aSet.Add(val)
}
bSet := set.NewSet()
for _, val := range b {
bSet.Add(val)
}
return aSet.IsEqual(bSet)
}
func HasMatchingItem(s1, s2 []string) bool {
roleMap := make(map[string]struct{})
for _, role := range s2 {
roleMap[role] = struct{}{}
}
for _, role := range s1 {
_, exists := roleMap[role]
if exists {
return true
}
}
return false
}
func RecoverLog(msg string) {
panc := recover()
if panc != nil {
logrus.WithFields(logrus.Fields{
"trace": string(debug.Stack()),
"panic": panc,
}).Error("sync: Panic in goroutine")
}
}
func CopyList(src *list.List) *list.List {
dst := list.New()
for x := src.Front(); x != nil; x = x.Next() {
dst.PushBack(x.Value)
}
return dst
}
func ToSnakeCase(s string) string {
var result []rune
for i, r := range s {
if i > 0 && unicode.IsUpper(r) {
result = append(result, '_')
}
result = append(result, unicode.ToLower(r))
}
return string(result)
}
func GetIntVer(version string) int {
re := regexp.MustCompile(`\d+`)
ver := re.FindAllString(version, -1)
if len(ver) == 0 {
return 0
}
lastNum, err := strconv.Atoi(ver[len(ver)-1])
if err != nil {
return 0
}
ver[len(ver)-1] = strconv.Itoa(lastNum + 4000)
var builder strings.Builder
for _, v := range ver {
num, err := strconv.Atoi(v)
if err != nil {
return 0
}
builder.WriteString(strings.Repeat(
"0", 4-len(strconv.Itoa(num))) + strconv.Itoa(num))
}
result, err := strconv.Atoi(builder.String())
if err != nil {
return 0
}
return result
}
================================================
FILE: utils/multilock.go
================================================
package utils
import (
"sync"
)
type MultiLock struct {
counts map[string]int
locks map[string]*sync.Mutex
lock sync.Mutex
}
func (m *MultiLock) Lock(id string) {
m.lock.Lock()
val := m.counts[id]
lock, ok := m.locks[id]
if !ok {
lock = &sync.Mutex{}
m.locks[id] = lock
}
m.counts[id] = val + 1
m.lock.Unlock()
lock.Lock()
}
func (m *MultiLock) Unlock(id string) {
m.lock.Lock()
val := m.counts[id]
lock := m.locks[id]
if val <= 1 {
delete(m.counts, id)
delete(m.locks, id)
} else {
m.counts[id] = val - 1
lock.Unlock()
}
m.lock.Unlock()
}
func (m *MultiLock) Locked(id string) bool {
m.lock.Lock()
_, ok := m.locks[id]
m.lock.Unlock()
return ok
}
func NewMultiLock() *MultiLock {
return &MultiLock{
counts: map[string]int{},
locks: map[string]*sync.Mutex{},
lock: sync.Mutex{},
}
}
================================================
FILE: utils/multitimeoutlock.go
================================================
package utils
import (
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
type MultiTimeoutLock struct {
counts map[string]int
locks map[string]*sync.Mutex
lock sync.Mutex
state map[bson.ObjectID]bool
stateLock sync.Mutex
timeout time.Duration
}
func (m *MultiTimeoutLock) Lock(id string) (lockId bson.ObjectID) {
m.lock.Lock()
val := m.counts[id]
lock, ok := m.locks[id]
if !ok {
lock = &sync.Mutex{}
m.locks[id] = lock
}
m.counts[id] = val + 1
m.lock.Unlock()
lock.Lock()
lockId = bson.NewObjectID()
m.stateLock.Lock()
m.state[lockId] = true
m.stateLock.Unlock()
if !constants.LockDebug {
return
}
start := time.Now()
go func() {
for {
time.Sleep(1 * time.Second)
m.stateLock.Lock()
state := m.state[lockId]
m.stateLock.Unlock()
if !state {
return
}
if time.Since(start) > m.timeout {
err := &errortypes.TimeoutError{
errors.New("utils: Multi lock timeout"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("utils: Multi lock timed out")
return
}
}
}()
return
}
func (m *MultiTimeoutLock) LockOpen(id string) (
acquired bool, lockId bson.ObjectID) {
m.lock.Lock()
val := m.counts[id]
lock, ok := m.locks[id]
if ok {
m.lock.Unlock()
return
}
lock = &sync.Mutex{}
m.locks[id] = lock
m.counts[id] = val + 1
m.lock.Unlock()
acquired = true
lock.Lock()
lockId = bson.NewObjectID()
m.stateLock.Lock()
m.state[lockId] = true
m.stateLock.Unlock()
if !constants.LockDebug {
return
}
start := time.Now()
go func() {
for {
time.Sleep(1 * time.Second)
m.stateLock.Lock()
state := m.state[lockId]
m.stateLock.Unlock()
if !state {
return
}
if time.Since(start) > m.timeout {
err := &errortypes.TimeoutError{
errors.New("utils: Multi lock timeout"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("utils: Multi lock timed out")
return
}
}
}()
return
}
func (m *MultiTimeoutLock) LockTimeout(id string,
timeout time.Duration) (lockId bson.ObjectID) {
m.lock.Lock()
val := m.counts[id]
lock, ok := m.locks[id]
if !ok {
lock = &sync.Mutex{}
m.locks[id] = lock
}
m.counts[id] = val + 1
m.lock.Unlock()
lock.Lock()
lockId = bson.NewObjectID()
m.stateLock.Lock()
m.state[lockId] = true
m.stateLock.Unlock()
if !constants.LockDebug {
return
}
start := time.Now()
go func() {
for {
time.Sleep(1 * time.Second)
m.stateLock.Lock()
state := m.state[lockId]
m.stateLock.Unlock()
if !state {
return
}
if time.Since(start) > timeout {
err := &errortypes.TimeoutError{
errors.New("utils: Multi lock timeout"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("utils: Multi lock timed out")
return
}
}
}()
return
}
func (m *MultiTimeoutLock) LockOpenTimeout(id string,
timeout time.Duration) (acquired bool, lockId bson.ObjectID) {
m.lock.Lock()
val := m.counts[id]
lock, ok := m.locks[id]
if ok {
m.lock.Unlock()
return
}
lock = &sync.Mutex{}
m.locks[id] = lock
m.counts[id] = val + 1
m.lock.Unlock()
acquired = true
lock.Lock()
lockId = bson.NewObjectID()
m.stateLock.Lock()
m.state[lockId] = true
m.stateLock.Unlock()
if !constants.LockDebug {
return
}
start := time.Now()
go func() {
for {
time.Sleep(1 * time.Second)
m.stateLock.Lock()
state := m.state[lockId]
m.stateLock.Unlock()
if !state {
return
}
if time.Since(start) > timeout {
err := &errortypes.TimeoutError{
errors.New("utils: Multi lock timeout"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("utils: Multi lock timed out")
return
}
}
}()
return
}
func (m *MultiTimeoutLock) Unlock(id string, lockId bson.ObjectID) {
m.lock.Lock()
val := m.counts[id]
lock := m.locks[id]
if val <= 1 {
delete(m.counts, id)
delete(m.locks, id)
} else {
m.counts[id] = val - 1
lock.Unlock()
}
m.lock.Unlock()
m.stateLock.Lock()
delete(m.state, lockId)
m.stateLock.Unlock()
}
func (m *MultiTimeoutLock) DelayUnlock(id string, lockId bson.ObjectID,
dur time.Duration) {
go func() {
time.Sleep(dur)
m.Unlock(id, lockId)
}()
}
func (m *MultiTimeoutLock) Locked(id string) bool {
m.lock.Lock()
_, ok := m.locks[id]
m.lock.Unlock()
return ok
}
func NewMultiTimeoutLock(timeout time.Duration) *MultiTimeoutLock {
return &MultiTimeoutLock{
counts: map[string]int{},
locks: map[string]*sync.Mutex{},
lock: sync.Mutex{},
state: map[bson.ObjectID]bool{},
stateLock: sync.Mutex{},
timeout: timeout,
}
}
================================================
FILE: utils/network.go
================================================
package utils
import (
"encoding/binary"
"io/ioutil"
"math/big"
"net"
"os"
"path"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var (
private10 = net.IPNet{
IP: net.IPv4(10, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
private100 = net.IPNet{
IP: net.IPv4(100, 64, 0, 0),
Mask: net.CIDRMask(10, 32),
}
private172 = net.IPNet{
IP: net.IPv4(172, 16, 0, 0),
Mask: net.CIDRMask(12, 32),
}
private192 = net.IPNet{
IP: net.IPv4(192, 168, 0, 0),
Mask: net.CIDRMask(16, 32),
}
private198 = net.IPNet{
IP: net.IPv4(198, 18, 0, 0),
Mask: net.CIDRMask(15, 32),
}
reserved6 = net.IPNet{
IP: net.IPv4(6, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved11 = net.IPNet{
IP: net.IPv4(11, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved21 = net.IPNet{
IP: net.IPv4(21, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved25 = net.IPNet{
IP: net.IPv4(25, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved26 = net.IPNet{
IP: net.IPv4(26, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved53 = net.IPNet{
IP: net.IPv4(53, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
reserved57 = net.IPNet{
IP: net.IPv4(57, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
loopback127 = net.IPNet{
IP: net.IPv4(127, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
linkLocal169 = net.IPNet{
IP: net.IPv4(169, 254, 0, 0),
Mask: net.CIDRMask(16, 32),
}
multicast224 = net.IPNet{
IP: net.IPv4(224, 0, 0, 0),
Mask: net.CIDRMask(4, 32),
}
broadcast255 = net.IPNet{
IP: net.IPv4(255, 255, 255, 255),
Mask: net.CIDRMask(32, 32),
}
zeroconf0 = net.IPNet{
IP: net.IPv4(0, 0, 0, 0),
Mask: net.CIDRMask(8, 32),
}
)
func IsPrivateIp(ip net.IP) bool {
if ip == nil {
return false
}
if ip.To4() == nil {
return (ip[0] & 0xfe) == 0xfc
}
if private10.Contains(ip) ||
private100.Contains(ip) ||
private172.Contains(ip) ||
private192.Contains(ip) ||
private198.Contains(ip) ||
reserved6.Contains(ip) ||
reserved11.Contains(ip) ||
reserved21.Contains(ip) ||
reserved25.Contains(ip) ||
reserved26.Contains(ip) ||
reserved53.Contains(ip) ||
reserved57.Contains(ip) {
return true
}
return false
}
func IsPublicIp(ip net.IP) bool {
if ip == nil {
return false
}
if ip.To4() == nil {
if (ip[0] & 0xfe) == 0xfc {
return false
}
if ip[0] == 0xfe && (ip[1]&0xc0) == 0x80 {
return false
}
if ip.Equal(net.IPv6loopback) {
return false
}
if ip.Equal(net.IPv6unspecified) {
return false
}
if ip[0] == 0xff {
return false
}
return true
}
if IsPrivateIp(ip) ||
loopback127.Contains(ip) ||
linkLocal169.Contains(ip) ||
multicast224.Contains(ip) ||
broadcast255.Contains(ip) ||
zeroconf0.Contains(ip) {
return false
}
return true
}
type Address struct {
Address net.IP
Network *net.IPNet
Ip6 bool
Private bool
Public bool
}
func ParseAddress(addrStr string) (addr *Address) {
addrStr = strings.TrimSpace(addrStr)
if addrStr == "" {
return
}
if strings.Contains(addrStr, "/") {
ip, network, err := net.ParseCIDR(addrStr)
if err != nil {
return
}
if ip == nil {
return
}
addr = &Address{
Address: ip,
Network: network,
Ip6: ip.To4() == nil,
Private: IsPrivateIp(ip),
Public: IsPublicIp(ip),
}
return
}
ip := net.ParseIP(addrStr)
if ip == nil {
return
}
addr = &Address{
Address: ip,
Ip6: ip.To4() == nil,
Private: IsPrivateIp(ip),
Public: IsPublicIp(ip),
}
return
}
func IncIpAddress(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}
func DecIpAddress(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]--
if ip[j] < 255 {
break
}
}
}
func CopyIpAddress(src net.IP) net.IP {
dst := make(net.IP, len(src))
copy(dst, src)
return dst
}
func IpAddress2BigInt(ip net.IP) (n *big.Int, bits int) {
n = &big.Int{}
n.SetBytes(ip)
if len(ip) == net.IPv4len {
bits = 32
} else {
bits = 128
}
return
}
func BigInt2IpAddress(n *big.Int, bits int) net.IP {
byt := n.Bytes()
ip := make([]byte, bits/8)
for i := 1; i <= len(byt); i++ {
ip[len(ip)-i] = byt[len(byt)-i]
}
return ip
}
func IpAddress2Int(ip net.IP) int64 {
if len(ip) == 16 {
return int64(binary.BigEndian.Uint32(ip[12:16]))
}
return int64(binary.BigEndian.Uint32(ip))
}
func Int2IpAddress(n int64) net.IP {
ip := make(net.IP, 4)
binary.BigEndian.PutUint32(ip, uint32(n))
return ip
}
func Int2IpIndex(n int64) (x int64, err error) {
if n%2 != 0 {
err = errortypes.ParseError{
errors.Newf("utils: Odd network int divide %d", n),
}
return
}
x = n / 2
return
}
func GetFirstIpIndex(network *net.IPNet) (n int64, err error) {
startIp := CopyIpAddress(network.IP)
startInt := IpAddress2Int(startIp)
startIndex, err := Int2IpIndex(startInt)
if err != nil {
return
}
n = startIndex + 1
return
}
func GetLastIpIndex(network *net.IPNet) (n int64, err error) {
endIp := GetLastIpAddress(network)
endInt := IpAddress2Int(endIp) - 1
endIndex, err := Int2IpIndex(endInt)
if err != nil {
return
}
n = endIndex - 1
return
}
func IpIndex2Ip(index int64) (x, y net.IP) {
x = Int2IpAddress(index * 2)
y = CopyIpAddress(x)
IncIpAddress(y)
return
}
func GetLastIpAddress(network *net.IPNet) net.IP {
prefixLen, bits := network.Mask.Size()
if prefixLen == bits {
return CopyIpAddress(network.IP)
}
start, bits := IpAddress2BigInt(network.IP)
n := uint(bits) - uint(prefixLen)
end := big.NewInt(1)
end.Lsh(end, n)
end.Sub(end, big.NewInt(1))
end.Or(end, start)
return BigInt2IpAddress(end, bits)
}
func NetworkContains(x, y *net.IPNet) bool {
return x.Contains(y.IP) && x.Contains(GetLastIpAddress(y))
}
func ParseIpMask(mask string) net.IPMask {
maskIp := net.ParseIP(mask)
if maskIp == nil {
return nil
}
return net.IPv4Mask(maskIp[12], maskIp[13], maskIp[14], maskIp[15])
}
func GetNamespaces() (namespaces []string, err error) {
items, err := ioutil.ReadDir("/var/run/netns")
if err != nil {
if os.IsNotExist(os.ErrNotExist) {
namespaces = []string{}
err = nil
} else {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read network namespaces"),
}
}
return
}
namespaces = []string{}
for _, item := range items {
namespaces = append(namespaces, item.Name())
}
return
}
func GetInterfaces() (ifaces []string, err error) {
ifaces, _, err = GetInterfacesSet()
if err != nil {
return
}
return
}
func GetInterfacesSet() (ifaces []string, ifacesSet set.Set, err error) {
items, err := ioutil.ReadDir("/sys/class/net")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read network interfaces"),
}
return
}
ifaces = []string{}
ifacesSet = set.NewSet()
for _, item := range items {
name := item.Name()
if name == "" {
continue
}
ifaces = append(ifaces, name)
ifacesSet.Add(name)
}
exists, err := ExistsDir("/etc/sysconfig/network-scripts")
if err != nil {
return
}
if exists {
items, err = ioutil.ReadDir("/etc/sysconfig/network-scripts")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read network scripts"),
}
return
}
for _, item := range items {
name := item.Name()
if !strings.HasPrefix(name, "ifcfg-") ||
!strings.Contains(name, ":") {
continue
}
name = name[6:]
names := strings.Split(name, ":")
if len(names) != 2 {
continue
}
if name == "" {
continue
}
if ifacesSet.Contains(names[0]) && !ifacesSet.Contains(name) {
ifaces = append(ifaces, name)
ifacesSet.Add(name)
}
}
}
return
}
func GetInterfaceUpper(iface string) (upper string, err error) {
iface = strings.Split(iface, ":")[0]
items, err := ioutil.ReadDir("/sys/class/net/" + iface)
if err != nil {
if os.IsNotExist(os.ErrNotExist) {
err = nil
} else {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read network interface"),
}
}
return
}
for _, item := range items {
name := item.Name()
if strings.HasPrefix(name, "upper_") {
upper = name[6:]
}
}
return
}
func IsInterfaceBridge(iface string) (bridge bool, err error) {
bridge, err = ExistsDir(
path.Join("/", "sys", "class", "net", iface, "bridge"))
if err != nil {
return
}
return
}
func FilterIp(input string) string {
input = strings.TrimSpace(input)
if input == "" {
return ""
}
ip := net.ParseIP(input)
if ip == nil {
return ""
}
return ip.String()
}
================================================
FILE: utils/proc.go
================================================
package utils
import (
"bytes"
"io"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/tools/commander"
"github.com/sirupsen/logrus"
)
var (
clockTicks = 0
)
func Exec(dir, name string, arg ...string) (err error) {
cmd := exec.Command(name, arg...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if dir != "" {
cmd.Dir = dir
}
err = cmd.Run()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
return
}
func ExecInput(dir, input, name string, arg ...string) (err error) {
cmd := exec.Command(name, arg...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
stdin, err := cmd.StdinPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err,
"utils: Failed to get stdin in exec '%s'", name),
}
return
}
if dir != "" {
cmd.Dir = dir
}
err = cmd.Start()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
var wrErr error
go func() {
defer func() {
wrErr = stdin.Close()
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to close stdin in exec '%s'",
name,
),
}
}
}()
_, wrErr = io.WriteString(stdin, input)
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to write stdin in exec '%s'",
name,
),
}
return
}
}()
err = cmd.Wait()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
if wrErr != nil {
err = wrErr
return
}
return
}
func ExecInputOutput(input, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
stdout := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = os.Stderr
stdin, err := cmd.StdinPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to get stdin in exec '%s'", name),
}
return
}
err = cmd.Start()
if err != nil {
stdin.Close()
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
var wrErr error
go func() {
defer func() {
wrErr = stdin.Close()
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to close stdin in exec '%s'",
name,
),
}
}
}()
_, wrErr = io.WriteString(stdin, input)
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to write stdin in exec '%s'",
name,
),
}
return
}
}()
err = cmd.Wait()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
if wrErr != nil {
err = wrErr
return
}
output = string(stdout.Bytes())
return
}
func ExecInputOutputCombindLogged(input, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = stderr
stdin, err := cmd.StdinPipe()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to get stdin in exec '%s'", name),
}
return
}
err = cmd.Start()
if err != nil {
stdin.Close()
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
var wrErr error
go func() {
defer func() {
wrErr = stdin.Close()
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to close stdin in exec '%s'",
name,
),
}
}
}()
_, wrErr = io.WriteString(stdin, input)
if wrErr != nil {
wrErr = &errortypes.ExecError{
errors.Wrapf(
wrErr,
"utils: Failed to write stdin in exec '%s'",
name,
),
}
return
}
}()
err = cmd.Wait()
output = stdout.String()
errOutput := stderr.String()
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
logrus.WithFields(logrus.Fields{
"output": output,
"error_output": errOutput,
"cmd": name,
"arg": arg,
"error": err,
}).Error("utils: Process exec error")
return
}
if wrErr != nil {
logrus.WithFields(logrus.Fields{
"output": output,
"error_output": errOutput,
"cmd": name,
"arg": arg,
"error": wrErr,
}).Error("utils: Process exec error")
return
}
output = string(stdout.Bytes())
return
}
func ExecOutput(dir, name string, arg ...string) (output string, err error) {
cmd := exec.Command(name, arg...)
cmd.Stderr = os.Stderr
if dir != "" {
cmd.Dir = dir
}
outputByt, err := cmd.Output()
if outputByt != nil {
output = string(outputByt)
}
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
return
}
func ExecCombinedOutput(dir, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
if dir != "" {
cmd.Dir = dir
}
outputByt, err := cmd.CombinedOutput()
if outputByt != nil {
output = string(outputByt)
}
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
return
}
return
}
func ExecCombinedOutputLogged(ignores []string, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
outputByt, err := cmd.CombinedOutput()
if outputByt != nil {
output = string(outputByt)
}
if err != nil && ignores != nil {
for _, ignore := range ignores {
if strings.Contains(output, ignore) {
err = nil
output = ""
break
}
}
}
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
logrus.WithFields(logrus.Fields{
"output": output,
"cmd": name,
"arg": arg,
"error": err,
}).Error("utils: Process exec error")
return
}
return
}
func ExecCombinedOutputLoggedDir(ignores []string,
dir, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
if dir != "" {
cmd.Dir = dir
}
outputByt, err := cmd.CombinedOutput()
if outputByt != nil {
output = string(outputByt)
}
if err != nil && ignores != nil {
for _, ignore := range ignores {
if strings.Contains(output, ignore) {
err = nil
output = ""
break
}
}
}
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
logrus.WithFields(logrus.Fields{
"output": output,
"cmd": name,
"arg": arg,
"error": err,
}).Error("utils: Process exec error")
return
}
return
}
func ExecOutputLogged(ignores []string, name string, arg ...string) (
output string, err error) {
cmd := exec.Command(name, arg...)
stdout := &bytes.Buffer{}
stderr := &bytes.Buffer{}
cmd.Stdout = stdout
cmd.Stderr = stderr
err = cmd.Run()
output = stdout.String()
errOutput := stderr.String()
if err != nil && ignores != nil {
for _, ignore := range ignores {
if strings.Contains(output, ignore) ||
strings.Contains(errOutput, ignore) {
err = nil
output = ""
break
}
}
}
if err != nil {
err = &errortypes.ExecError{
errors.Wrapf(err, "utils: Failed to exec '%s'", name),
}
logrus.WithFields(logrus.Fields{
"output": output,
"error_output": errOutput,
"cmd": name,
"arg": arg,
"error": err,
}).Error("utils: Process exec error")
return
}
return
}
func getClockTicks() (ticks int) {
if clockTicks != 0 {
ticks = clockTicks
return
}
resp, err := commander.Exec(&commander.Opt{
Name: "getconf",
Args: []string{"CLK_TCK"},
PipeOut: true,
PipeErr: true,
})
if err != nil {
ticks = 100
clockTicks = 100
return
}
if resp.Output != nil {
ticks, _ = strconv.Atoi(strings.TrimSpace(string(resp.Output)))
}
if ticks == 0 {
ticks = 100
clockTicks = 100
return
}
clockTicks = ticks
return
}
func GetProcessTimestamp(pid int) (timestamp time.Time, err error) {
procPath := filepath.Join("/proc", strconv.Itoa(pid))
_, err = os.Stat(procPath)
if os.IsNotExist(err) {
err = nil
return
}
statPath := filepath.Join(procPath, "stat")
statData, err := os.ReadFile(statPath)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read process stat"),
}
return
}
statFields := strings.Fields(string(statData))
if len(statFields) < 22 {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Invalid process state format"),
}
return
}
startTimeTicks, err := strconv.ParseInt(statFields[21], 10, 64)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to process stat"),
}
return
}
uptimeData, err := os.ReadFile("/proc/uptime")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read uptime"),
}
return
}
uptimeFields := strings.Fields(string(uptimeData))
if len(uptimeFields) < 1 {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Invalid uptime format"),
}
return
}
systemUptimeSec, err := strconv.ParseFloat(uptimeFields[0], 64)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to process uptime"),
}
return
}
processStartTimeSec := float64(startTimeTicks) / float64(getClockTicks())
processUptimeSec := systemUptimeSec - processStartTimeSec
timestamp = time.Now().Add(
time.Duration(processUptimeSec * -float64(time.Second)))
return
}
================================================
FILE: utils/prompt.go
================================================
package utils
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
var (
AssumeYes = false
)
func parseYesNo(input string) (val bool, err error) {
input = strings.ToLower(input)
if input == "y" || input == "yes" {
val = true
return
} else if input == "n" || input == "no" {
val = false
return
}
err = &errortypes.ParseError{
errors.New("prompt: Invalid confirm input"),
}
return
}
func ConfirmDefault(label string, def bool) (resp bool, err error) {
if AssumeYes {
resp = true
return
}
var prompt string
if def {
prompt = fmt.Sprintf("%s [Y/n]: ", label)
} else {
prompt = fmt.Sprintf("%s [y/N]: ", label)
}
fmt.Print(prompt)
reader := bufio.NewReader(os.Stdin)
input, err := reader.ReadString('\n')
if err != nil {
return
}
input = strings.TrimSpace(input)
if input == "" {
resp = def
return
}
resp, err = parseYesNo(input)
if err != nil {
return
}
return
}
================================================
FILE: utils/psutil_freebsd.go
================================================
package utils
import (
"encoding/binary"
"runtime"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"golang.org/x/sys/unix"
)
type MemInfo struct {
Total uint64
Free uint64
Available uint64
Buffers uint64
Cached uint64
Used uint64
UsedPercent float64
Dirty uint64
SwapTotal uint64
SwapFree uint64
SwapUsed uint64
SwapUsedPercent float64
HugePagesTotal uint64
HugePagesFree uint64
HugePagesReserved uint64
HugePagesUsed uint64
HugePagesUsedPercent float64
HugePageSize uint64
}
func getSysctlUint64(name string) (uint64, error) {
value32, err := unix.SysctlUint32(name)
if err == nil {
return uint64(value32), nil
}
return unix.SysctlUint64(name)
}
func GetMemInfo() (info *MemInfo, err error) {
info = &MemInfo{}
totalMem, err := getSysctlUint64("hw.physmem")
if err != nil {
return nil, &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read physmem"),
}
}
info.Total = totalMem / 1024
pageSize, err := getSysctlUint64("hw.pagesize")
if err != nil {
return nil, &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read pagesize"),
}
}
freePages, err := getSysctlUint64("vm.stats.vm.v_free_count")
if err != nil {
return nil, &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read freecount"),
}
}
info.Free = (uint64(freePages) * uint64(pageSize)) / 1024
info.Available = info.Free
info.Used = info.Total - info.Free
if info.Total > 0 {
info.UsedPercent = float64(info.Used) / float64(info.Total) * 100.0
}
return info, nil
}
type LoadStat struct {
CpuUnits int
Load1 float64
Load5 float64
Load15 float64
}
func LoadAverage() (ld *LoadStat, err error) {
count := runtime.NumCPU()
countFloat := float64(count)
loadavgRaw, err := unix.SysctlRaw("vm.loadavg")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read loadavg"),
}
return
}
if len(loadavgRaw) < 12 {
err = &errortypes.ReadError{
errors.New("utils: Invalid loadavg size"),
}
return
}
fscale := float64(1 << 11)
if len(loadavgRaw) >= 20 {
readFscale := float64(binary.LittleEndian.Uint32(loadavgRaw[16:20]))
if readFscale > 0 {
fscale = readFscale
}
}
load1 := float64(binary.LittleEndian.Uint32(loadavgRaw[0:4])) / fscale
load5 := float64(binary.LittleEndian.Uint32(loadavgRaw[4:8])) / fscale
load15 := float64(binary.LittleEndian.Uint32(loadavgRaw[8:12])) / fscale
ld = &LoadStat{
CpuUnits: count,
Load1: ToFixed(load1/countFloat*100, 2),
Load5: ToFixed(load5/countFloat*100, 2),
Load15: ToFixed(load15/countFloat*100, 2),
}
return
}
================================================
FILE: utils/psutil_linux.go
================================================
package utils
import (
"io/ioutil"
"runtime"
"strconv"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
)
type MemInfo struct {
Total uint64
Free uint64
Available uint64
Buffers uint64
Cached uint64
Used uint64
UsedPercent float64
Dirty uint64
SwapTotal uint64
SwapFree uint64
SwapUsed uint64
SwapUsedPercent float64
HugePagesTotal uint64
HugePagesFree uint64
HugePagesReserved uint64
HugePagesUsed uint64
HugePagesUsedPercent float64
HugePageSize uint64
}
func GetMemInfo() (info *MemInfo, err error) {
info = &MemInfo{}
lines, err := ReadLines("/proc/meminfo")
if err != nil {
return
}
for _, line := range lines {
fields := strings.Split(line, ":")
if len(fields) != 2 {
continue
}
key := strings.TrimSpace(fields[0])
value := strings.TrimSpace(fields[1])
value = strings.Replace(value, " kB", "", -1)
switch key {
case "MemTotal":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse mem total"),
}
return
}
info.Total = valueInt
case "MemFree":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse mem free"),
}
return
}
info.Free = valueInt
case "MemAvailable":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse mem available"),
}
return
}
info.Available = valueInt
case "Buffers":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse buffers"),
}
return
}
info.Buffers = valueInt
case "Cached":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse cached"),
}
return
}
info.Cached = valueInt
case "Dirty":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse dirty"),
}
return
}
info.Dirty = valueInt
case "SwapTotal":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse swap total"),
}
return
}
info.SwapTotal = valueInt
case "SwapFree":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse swap free"),
}
return
}
info.SwapFree = valueInt
case "HugePages_Total":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse hugepages total"),
}
return
}
info.HugePagesTotal = valueInt
case "HugePages_Free":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse hugepages total"),
}
return
}
info.HugePagesFree = valueInt
case "HugePages_Rsvd":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e,
"utils: Failed to parse hugepages reserved"),
}
return
}
info.HugePagesReserved = valueInt
case "Hugepagesize":
valueInt, e := strconv.ParseUint(value, 10, 64)
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "utils: Failed to parse hugepages size"),
}
return
}
info.HugePageSize = valueInt
}
}
info.Used = info.Total - info.Free - info.Buffers - info.Cached
info.UsedPercent = float64(info.Used) / float64(info.Total) * 100.0
info.SwapUsed = info.SwapTotal - info.SwapFree
if info.SwapUsed != 0 {
info.SwapUsedPercent = float64(
info.SwapUsed) / float64(info.SwapTotal) * 100.0
}
info.HugePagesUsed = (info.HugePagesTotal - info.HugePagesFree) +
info.HugePagesReserved
if info.HugePagesUsed != 0 {
info.HugePagesUsedPercent = float64(
info.HugePagesUsed) / float64(info.HugePagesTotal) * 100.0
}
return
}
type LoadStat struct {
CpuUnits int
Load1 float64
Load5 float64
Load15 float64
}
func LoadAverage() (ld *LoadStat, err error) {
count := runtime.NumCPU()
countFloat := float64(count)
_ = countFloat
ld = &LoadStat{}
line, err := ioutil.ReadFile("/proc/loadavg")
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "utils: Failed to read loadavg"),
}
return
}
values := strings.Fields(string(line))
if len(values) < 3 {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Invalid loadavg data"),
}
return
}
load1, err := strconv.ParseFloat(values[0], 64)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Invalid load1 data"),
}
return
}
load5, err := strconv.ParseFloat(values[1], 64)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Invalid load5 data"),
}
return
}
load15, err := strconv.ParseFloat(values[2], 64)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "utils: Invalid load15 data"),
}
return
}
ld = &LoadStat{
CpuUnits: count,
Load1: ToFixed(load1/countFloat*100, 2),
Load5: ToFixed(load5/countFloat*100, 2),
Load15: ToFixed(load15/countFloat*100, 2),
}
return
}
================================================
FILE: utils/randomname.go
================================================
package utils
import (
"bytes"
"fmt"
"math/rand"
)
var (
randElm = []string{
"copper",
"argon",
"xenon",
"radon",
"cobalt",
"nickel",
"carbon",
"helium",
"nitrogen",
"radium",
"lithium",
"silicon",
}
)
func RandName() (name string) {
name = fmt.Sprintf("%s-%d", randElm[rand.Intn(len(randElm))],
rand.Intn(8999)+1000)
return
}
func RandIp() string {
return fmt.Sprintf("26.197.%d.%d", rand.Intn(250)+4, rand.Intn(250)+4)
}
func RandIp6() (addr string) {
addr = "2604:4080"
randByt, _ := RandBytes(12)
randHex := fmt.Sprintf("%x", randByt)
buf := bytes.Buffer{}
for i, run := range randHex {
if i%4 == 0 && i != len(randHex)-1 {
buf.WriteRune(':')
}
buf.WriteRune(run)
}
addr += buf.String()
return
}
func RandPrivateIp() string {
return fmt.Sprintf("10.232.%d.%d", rand.Intn(250)+4, rand.Intn(250)+4)
}
func RandPrivateIp6() (addr string) {
addr = "fd97:7d1d"
randByt, _ := RandBytes(12)
randHex := fmt.Sprintf("%x", randByt)
buf := bytes.Buffer{}
for i, run := range randHex {
if i%4 == 0 && i != len(randHex)-1 {
buf.WriteRune(':')
}
buf.WriteRune(run)
}
addr += buf.String()
return
}
================================================
FILE: utils/request.go
================================================
package utils
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"strings"
"github.com/dropbox/godropbox/errors"
"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/render"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
type NopCloser struct {
io.Reader
}
func (NopCloser) Close() error {
return nil
}
var httpErrCodes = map[int]string{
400: "Bad Request",
401: "Unauthorized",
402: "Payment Required",
403: "Forbidden",
404: "Not Found",
405: "Method Not Allowed",
406: "Not Acceptable",
407: "Proxy Authentication Required",
408: "Request Timeout",
409: "Conflict",
410: "Gone",
411: "Length Required",
412: "Precondition Failed",
413: "Payload Too Large",
414: "URI Too Long",
415: "Unsupported Media Type",
416: "Range Not Satisfiable",
417: "Expectation Failed",
421: "Misdirected Request",
422: "Unprocessable Entity",
423: "Locked",
424: "Failed Dependency",
426: "Upgrade Required",
428: "Precondition Required",
429: "Too Many Requests",
431: "Request Header Fields Too Large",
451: "Unavailable For Legal Reasons",
500: "Internal Server Error",
501: "Not Implemented",
502: "Bad Gateway",
503: "Service Unavailable",
504: "Gateway Timeout",
505: "HTTP Version Not Supported",
506: "Variant Also Negotiates",
507: "Insufficient Storage",
508: "Loop Detected",
510: "Not Extended",
511: "Network Authentication Required",
}
func CopyBody(r *http.Request) (buffer *bytes.Buffer, err error) {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "handler: Request read error"),
}
return
}
_ = r.Body.Close()
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
buffer = bytes.NewBuffer(body)
return
}
func StripPort(hostport string) string {
colon := strings.IndexByte(hostport, ':')
if colon == -1 {
return hostport
}
n := strings.Count(hostport, ":")
if n > 1 {
if i := strings.IndexByte(hostport, ']'); i != -1 {
return strings.TrimPrefix(hostport[:i], "[")
}
return hostport
}
return hostport[:colon]
}
func FormatHostPort(hostname string, port int) string {
if strings.Contains(hostname, ":") {
hostname = "[" + hostname + "]"
}
return fmt.Sprintf("%s:%d", hostname, port)
}
func ParseObjectId(strId string) (objId bson.ObjectID, ok bool) {
if strId == "" {
objId = bson.NilObjectID
return
}
objectId, err := bson.ObjectIDFromHex(strId)
if err != nil {
objId = bson.NilObjectID
return
}
objId = objectId
ok = true
return
}
func ObjectIdHex(strId string) (objId bson.ObjectID) {
if strId == "" {
objId = bson.NilObjectID
return
}
objectId, err := bson.ObjectIDFromHex(strId)
if err != nil {
objId = bson.NilObjectID
return
}
objId = objectId
return
}
func GetStatusMessage(code int) string {
return fmt.Sprintf("%d %s", code, http.StatusText(code))
}
func AbortWithStatus(c *gin.Context, code int) {
r := render.String{
Format: GetStatusMessage(code),
}
c.Status(code)
r.WriteContentType(c.Writer)
c.Writer.WriteHeaderNow()
r.Render(c.Writer)
c.Abort()
}
func AbortWithError(c *gin.Context, code int, err error) {
AbortWithStatus(c, code)
c.Error(err)
}
func WriteStatus(w http.ResponseWriter, code int) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
fmt.Fprintln(w, GetStatusMessage(code))
}
func WriteText(w http.ResponseWriter, code int, text string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(code)
fmt.Fprintln(w, text)
}
func WriteUnauthorized(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Header().Set("X-Content-Type-Options", "nosniff")
w.WriteHeader(401)
fmt.Fprintln(w, "401 "+msg)
}
func CloneHeader(src http.Header) (dst http.Header) {
dst = make(http.Header, len(src))
for k, vv := range src {
vv2 := make([]string, len(vv))
copy(vv2, vv)
dst[k] = vv2
}
return dst
}
func GetLocation(r *http.Request, domains []string) string {
host := ""
if r.Host != "" {
host = r.Host
} else if r.URL.Host != "" {
host = r.URL.Host
}
if host != "" {
for _, domain := range domains {
if domain == host {
return "https://" + domain
}
}
}
for _, domain := range domains {
if domain != "" {
return "https://" + domain
}
}
return ""
}
func GetOrigin(r *http.Request) string {
origin := r.Header.Get("Origin")
if origin == "" {
host := ""
switch {
case r.Host != "":
host = r.Host
break
case r.URL.Host != "":
host = r.URL.Host
break
}
origin = "https://" + host
}
return origin
}
func CheckRequestN(resp *http.Response, msg string, codes []int) (err error) {
for _, code := range codes {
if resp.StatusCode == code {
return
}
}
bodyStr := ""
bodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, 10*1024))
if readErr != nil {
bodyStr = fmt.Sprintf("[%v]", readErr)
} else {
bodyStr = string(bodyBytes)
}
logrus.WithFields(logrus.Fields{
"body": bodyStr,
"status_code": resp.StatusCode,
"message": msg,
}).Error(msg)
err = &errortypes.RequestError{
errors.Newf("request: Response status error %d", resp.StatusCode),
}
return
}
func CheckRequest(resp *http.Response, msg string) (err error) {
if resp.StatusCode == 200 {
return
}
bodyStr := ""
bodyBytes, readErr := io.ReadAll(io.LimitReader(resp.Body, 10*1024))
if readErr != nil {
bodyStr = fmt.Sprintf("[%v]", readErr)
} else {
bodyStr = string(bodyBytes)
}
logrus.WithFields(logrus.Fields{
"body": bodyStr,
"status_code": resp.StatusCode,
"message": msg,
}).Error(msg)
err = &errortypes.RequestError{
errors.Newf("request: Response status error %d", resp.StatusCode),
}
return
}
================================================
FILE: utils/sort.go
================================================
package utils
import (
"regexp"
"sort"
"strconv"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
var (
numRe = regexp.MustCompile(`(\d+|\D+)`)
)
type ObjectIdSlice []bson.ObjectID
func (o ObjectIdSlice) Len() int {
return len(o)
}
func (o ObjectIdSlice) Less(i, j int) bool {
return o[i].Hex() < o[j].Hex()
}
func (o ObjectIdSlice) Swap(i, j int) {
o[i], o[j] = o[j], o[i]
}
func SortObjectIds(x []bson.ObjectID) {
sort.Sort(ObjectIdSlice(x))
}
func NaturalCompare(a, b string) int {
aParts := numRe.FindAllString(a, -1)
bParts := numRe.FindAllString(b, -1)
minLen := len(aParts)
if len(bParts) < minLen {
minLen = len(bParts)
}
for i := 0; i < minLen; i++ {
aPart := aParts[i]
bPart := bParts[i]
aNum, aErr := strconv.Atoi(aPart)
bNum, bErr := strconv.Atoi(bPart)
if aErr == nil && bErr == nil {
if aNum != bNum {
return aNum - bNum
}
} else if (aErr == nil) != (bErr == nil) {
if aErr == nil {
return -1
}
return 1
} else {
if aPart != bPart {
return strings.Compare(aPart, bPart)
}
}
}
return len(aParts) - len(bParts)
}
================================================
FILE: utils/timeoutlock.go
================================================
package utils
import (
"sync"
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/constants"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/sirupsen/logrus"
)
type TimeoutLock struct {
lock sync.Mutex
state map[bson.ObjectID]bool
stateLock sync.Mutex
timeout time.Duration
}
func (l *TimeoutLock) Lock() (id bson.ObjectID) {
id = bson.NewObjectID()
l.lock.Lock()
l.stateLock.Lock()
l.state[id] = true
l.stateLock.Unlock()
if !constants.LockDebug {
return
}
start := time.Now()
go func() {
for {
time.Sleep(1 * time.Second)
l.stateLock.Lock()
state := l.state[id]
l.stateLock.Unlock()
if !state {
return
}
if time.Since(start) > l.timeout {
err := &errortypes.TimeoutError{
errors.New("utils: Multi lock timeout"),
}
logrus.WithFields(logrus.Fields{
"error": err,
}).Error("utils: Lock timed out")
return
}
}
}()
return
}
func (l *TimeoutLock) Unlock(id bson.ObjectID) {
l.lock.Unlock()
l.stateLock.Lock()
delete(l.state, id)
l.stateLock.Unlock()
}
func NewTimeoutLock(timeout time.Duration) *TimeoutLock {
return &TimeoutLock{
lock: sync.Mutex{},
state: map[bson.ObjectID]bool{},
stateLock: sync.Mutex{},
timeout: timeout,
}
}
================================================
FILE: utils/unix.go
================================================
package utils
import (
"crypto/sha512"
"strconv"
)
const b64x24Chars = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
func Base64x24(src []byte) (hash []byte) {
if len(src) == 0 {
return []byte{}
}
hashSize := (len(src) * 8) / 6
if (len(src) % 6) != 0 {
hashSize += 1
}
hash = make([]byte, hashSize)
dst := hash
for len(src) > 0 {
switch len(src) {
default:
dst[0] = b64x24Chars[src[0]&0x3f]
dst[1] = b64x24Chars[((src[0]>>6)|(src[1]<<2))&0x3f]
dst[2] = b64x24Chars[((src[1]>>4)|(src[2]<<4))&0x3f]
dst[3] = b64x24Chars[(src[2]>>2)&0x3f]
src = src[3:]
dst = dst[4:]
case 2:
dst[0] = b64x24Chars[src[0]&0x3f]
dst[1] = b64x24Chars[((src[0]>>6)|(src[1]<<2))&0x3f]
dst[2] = b64x24Chars[(src[1]>>4)&0x3f]
src = src[2:]
dst = dst[3:]
case 1:
dst[0] = b64x24Chars[src[0]&0x3f]
dst[1] = b64x24Chars[(src[0]>>6)&0x3f]
src = src[1:]
dst = dst[2:]
}
}
return
}
func GenerateShadow(passwd string) (output string, err error) {
var i int
rounds := 4096
saltStr, err := RandStr(8)
if err != nil {
return
}
salt := []byte(saltStr)
passwdByt := []byte(passwd)
alternateHash := sha512.New()
alternateHash.Write(passwdByt)
alternateHash.Write(salt)
alternateHash.Write(passwdByt)
alernateSum := alternateHash.Sum(nil)
aSeqHash := sha512.New()
aSeqHash.Write(passwdByt)
aSeqHash.Write(salt)
for i = len(passwdByt); i > 64; i -= 64 {
aSeqHash.Write(alernateSum)
}
aSeqHash.Write(alernateSum[0:i])
for i = len(passwdByt); i > 0; i >>= 1 {
if (i & 1) != 0 {
aSeqHash.Write(alernateSum)
} else {
aSeqHash.Write(passwdByt)
}
}
aSeqSum := aSeqHash.Sum(nil)
pSeqHash := sha512.New()
for i = 0; i < len(passwdByt); i++ {
pSeqHash.Write(passwdByt)
}
pSeqSum := pSeqHash.Sum(nil)
pSeq := make([]byte, 0, len(passwdByt))
for i = len(passwdByt); i > 64; i -= 64 {
pSeq = append(pSeq, pSeqSum...)
}
pSeq = append(pSeq, pSeqSum[0:i]...)
sSeqHash := sha512.New()
for i = 0; i < (16 + int(aSeqSum[0])); i++ {
sSeqHash.Write(salt)
}
sSeqSum := sSeqHash.Sum(nil)
sSeq := make([]byte, 0, len(salt))
for i = len(salt); i > 64; i -= 64 {
sSeq = append(sSeq, sSeqSum...)
}
sSeq = append(sSeq, sSeqSum[0:i]...)
cSum := aSeqSum
for i = 0; i < rounds; i++ {
C := sha512.New()
if (i & 1) != 0 {
C.Write(pSeq)
} else {
C.Write(cSum)
}
if (i % 3) != 0 {
C.Write(sSeq)
}
if (i % 7) != 0 {
C.Write(pSeq)
}
if (i & 1) != 0 {
C.Write(cSum)
} else {
C.Write(pSeq)
}
cSum = C.Sum(nil)
}
out := make([]byte, 0, 123)
out = append(out, "$6$"...)
out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...)
out = append(out, salt...)
out = append(out, '$')
out = append(out, Base64x24([]byte{
cSum[42], cSum[21], cSum[0],
cSum[1], cSum[43], cSum[22],
cSum[23], cSum[2], cSum[44],
cSum[45], cSum[24], cSum[3],
cSum[4], cSum[46], cSum[25],
cSum[26], cSum[5], cSum[47],
cSum[48], cSum[27], cSum[6],
cSum[7], cSum[49], cSum[28],
cSum[29], cSum[8], cSum[50],
cSum[51], cSum[30], cSum[9],
cSum[10], cSum[52], cSum[31],
cSum[32], cSum[11], cSum[53],
cSum[54], cSum[33], cSum[12],
cSum[13], cSum[55], cSum[34],
cSum[35], cSum[14], cSum[56],
cSum[57], cSum[36], cSum[15],
cSum[16], cSum[58], cSum[37],
cSum[38], cSum[17], cSum[59],
cSum[60], cSum[39], cSum[18],
cSum[19], cSum[61], cSum[40],
cSum[41], cSum[20], cSum[62],
cSum[63],
})...)
aSeqHash.Reset()
alternateHash.Reset()
pSeqHash.Reset()
for i = 0; i < len(aSeqSum); i++ {
aSeqSum[i] = 0
}
for i = 0; i < len(alernateSum); i++ {
alernateSum[i] = 0
}
for i = 0; i < len(pSeq); i++ {
pSeq[i] = 0
}
output = string(out)
return
}
================================================
FILE: utils/webauthn.go
================================================
package utils
import (
"fmt"
"github.com/dropbox/godropbox/errors"
"github.com/go-webauthn/webauthn/protocol"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func ParseWebauthnError(err error) (newErr error) {
if e, ok := err.(*protocol.Error); ok {
newErr = &errortypes.AuthenticationError{
errors.Wrapf(
err, "secondary: Webauthn error %s - %s - %s",
e.Type, e.DevInfo, e.Details,
),
}
} else {
newErr = &errortypes.AuthenticationError{
errors.Wrap(err, fmt.Sprintf(
"secondary: Webauthn unknown error")),
}
}
return
}
================================================
FILE: validator/validator.go
================================================
package validator
import (
"net/http"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/audit"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/event"
"github.com/pritunl/pritunl-cloud/policy"
"github.com/pritunl/pritunl-cloud/user"
)
func ValidateAdmin(db *database.Database, usr *user.User,
isApi bool, r *http.Request) (deviceAuth bool,
secProvider bson.ObjectID, errAudit audit.Fields,
errData *errortypes.ErrorData, err error) {
if !usr.ActiveUntil.IsZero() && usr.ActiveUntil.Before(time.Now()) {
usr.ActiveUntil = time.Time{}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("active_until", "disabled"))
if err != nil {
return
}
event.PublishDispatch(db, "user.change")
errAudit = audit.Fields{
"error": "user_disabled",
"message": "User is disabled from expired active time",
}
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
return
}
if usr.Disabled {
errAudit = audit.Fields{
"error": "user_disabled",
"message": "User is disabled",
}
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
return
}
if usr.Administrator != "super" {
errAudit = audit.Fields{
"error": "user_not_super",
"message": "User is not super user",
}
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
return
}
if !isApi {
policies, e := policy.GetRoles(db, usr.Roles)
if e != nil {
err = e
return
}
for _, polcy := range policies {
errData, err = polcy.ValidateUser(db, usr, r)
if err != nil || errData != nil {
return
}
}
for _, polcy := range policies {
if polcy.Disabled {
continue
}
if polcy.AdminDeviceSecondary {
deviceAuth = true
}
if !polcy.AdminSecondary.IsZero() && secProvider.IsZero() {
secProvider = polcy.AdminSecondary
}
}
}
return
}
func ValidateUser(db *database.Database, usr *user.User,
isApi bool, r *http.Request) (deviceAuth bool,
secProvider bson.ObjectID, errAudit audit.Fields,
errData *errortypes.ErrorData, err error) {
if !usr.ActiveUntil.IsZero() && usr.ActiveUntil.Before(time.Now()) {
usr.ActiveUntil = time.Time{}
usr.Disabled = true
err = usr.CommitFields(db, set.NewSet("active_until", "disabled"))
if err != nil {
return
}
event.PublishDispatch(db, "user.change")
errAudit = audit.Fields{
"error": "user_disabled",
"message": "User is disabled from expired active time",
}
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
return
}
if usr.Disabled {
errAudit = audit.Fields{
"error": "user_disabled",
"message": "User is disabled",
}
errData = &errortypes.ErrorData{
Error: "unauthorized",
Message: "Not authorized",
}
return
}
if !isApi {
policies, e := policy.GetRoles(db, usr.Roles)
if e != nil {
err = e
return
}
for _, polcy := range policies {
errData, err = polcy.ValidateUser(db, usr, r)
if err != nil || errData != nil {
return
}
}
for _, polcy := range policies {
if polcy.Disabled {
continue
}
if polcy.UserDeviceSecondary {
deviceAuth = true
}
if !polcy.UserSecondary.IsZero() && secProvider.IsZero() {
secProvider = polcy.UserSecondary
}
}
}
return
}
================================================
FILE: version/cache.go
================================================
package version
import (
"sync"
"time"
)
var (
cacheStore = map[string]*cache{}
cacheLock = sync.Mutex{}
)
const (
cacheTtl = 5 * time.Minute
)
type cache struct {
Version int
Timestamp time.Time
}
func cacheCheck(module string, ver int) (supported bool) {
cacheLock.Lock()
defer cacheLock.Unlock()
cach, ok := cacheStore[module]
if !ok {
return true
}
if time.Since(cach.Timestamp) > cacheTtl {
delete(cacheStore, module)
return true
}
return ver >= cach.Version
}
func cacheSet(module string, ver int) {
cacheLock.Lock()
defer cacheLock.Unlock()
cacheStore[module] = &cache{
Version: ver,
Timestamp: time.Now(),
}
existing, ok := cacheStore[module]
if !ok || ver > existing.Version {
cacheStore[module] = &cache{
Version: ver,
Timestamp: time.Now(),
}
}
}
================================================
FILE: version/utils.go
================================================
package version
import (
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
)
func Check(db *database.Database, module string, ver int) (
supported bool, err error) {
if !cacheCheck(module, ver) {
return false, nil
}
coll := db.Versions()
vr := &Version{}
err = coll.FindOneId(module, vr)
if err != nil {
if _, ok := err.(*database.NotFoundError); ok {
vr = nil
err = nil
} else {
return
}
}
if vr == nil || ver >= vr.Version {
supported = true
return
}
cacheSet(module, vr.Version)
return
}
func Set(db *database.Database, module string, ver int) (err error) {
coll := db.Versions()
opts := options.UpdateOne().
SetUpsert(true)
_, err = coll.UpdateOne(
db,
&bson.M{
"_id": module,
},
&bson.M{
"$max": &bson.M{
"version": ver,
},
"$setOnInsert": &bson.M{
"_id": module,
},
},
opts,
)
if err != nil {
err = database.ParseError(err)
return
}
return
}
================================================
FILE: version/version.go
================================================
package version
type Version struct {
Module string `bson:"_id,omitempty" json:"id"`
Version int `bson:"version,omitempty" json:"version"`
}
================================================
FILE: virtiofs/systemd.go
================================================
package virtiofs
import (
"fmt"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
const systemdTemplate = `[Unit]
Description=Pritunl Cloud VirtIO-FS Daemon
After=network.target
[Service]
Type=simple
User=root
ExecStart=%s --socket-path="%s" --shared-dir="%s" --sandbox=namespace
TimeoutStopSec=5
PrivateTmp=true
ProtectSystem=full
ProtectHostname=true
ProtectKernelTunables=true
`
func WriteService(vmId bson.ObjectID,
shareId, sharePath string) (err error) {
unitPath := paths.GetUnitPathShare(vmId, shareId)
sockPath := paths.GetShareSockPath(vmId, shareId)
output := fmt.Sprintf(
systemdTemplate,
GetVirtioFsdPath(),
sockPath,
sharePath,
)
err = utils.CreateWrite(unitPath, output, 0600)
if err != nil {
return
}
return
}
func Start(db *database.Database, virt *vm.VirtualMachine,
shareId, sharePath string) (err error) {
unit := paths.GetUnitNameShare(virt.Id, shareId)
logrus.WithFields(logrus.Fields{
"id": virt.Id.Hex(),
"systemd_unit": unit,
}).Info("virtiofs: Starting virtual machine virtiofsd")
_ = systemd.Stop(unit)
err = WriteService(virt.Id, shareId, sharePath)
if err != nil {
return
}
err = systemd.Reload()
if err != nil {
return
}
err = systemd.Start(unit)
if err != nil {
return
}
return
}
func Stop(virt *vm.VirtualMachine, shareId string) (err error) {
unit := paths.GetUnitNameShare(virt.Id, shareId)
_ = systemd.Stop(unit)
return
}
================================================
FILE: virtiofs/utils.go
================================================
package virtiofs
import (
"github.com/pritunl/pritunl-cloud/utils"
)
const (
Libexec = "/usr/libexec/virtiofsd"
System = "/usr/bin/virtiofsd"
)
func GetVirtioFsdPath() string {
exists, _ := utils.Exists(System)
if exists {
return System
}
return Libexec
}
================================================
FILE: virtiofs/virtiofs.go
================================================
package virtiofs
import (
"time"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/organization"
"github.com/pritunl/pritunl-cloud/paths"
"github.com/pritunl/pritunl-cloud/systemd"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
)
func StartAll(db *database.Database,
virt *vm.VirtualMachine) (err error) {
mounts := []*vm.Mount{}
unitPathShares := paths.GetUnitPathShares(virt.Id)
_, err = utils.RemoveWildcard(unitPathShares)
if err != nil {
return
}
if len(virt.Mounts) == 0 {
return
}
org, err := organization.Get(db, virt.Organization)
if err != nil {
return
}
if org == nil {
err = &errortypes.ParseError{
errors.New("virtiofs: Failed to get org"),
}
return
}
for _, mount := range virt.Mounts {
matchPath := false
matchRoles := false
for _, share := range node.Self.Shares {
if share.MatchPath(mount.HostPath) {
matchPath = true
if utils.HasMatchingItem(share.Roles, org.Roles) {
matchRoles = true
break
}
}
}
if !matchPath && !matchRoles {
err = &errortypes.ParseError{
errors.Newf("virtiofs: Failed to find matching "+
"share path for mount '%s'", mount.HostPath),
}
return
}
if !matchPath || !matchRoles {
err = &errortypes.ParseError{
errors.Newf("virtiofs: Failed to find matching "+
"role for mount '%s'", mount.HostPath),
}
return
}
mounts = append(mounts, mount)
}
for _, mount := range mounts {
shareId := paths.GetShareId(virt.Id, mount.Name)
err = Start(db, virt, shareId, mount.HostPath)
if err != nil {
return
}
}
time.Sleep(1 * time.Second)
return
}
func StopAll(virt *vm.VirtualMachine) (err error) {
unit := paths.GetUnitNameShares(virt.Id)
_ = systemd.Stop(unit)
return
}
================================================
FILE: vm/constants.go
================================================
package vm
const (
Starting = "starting"
Running = "running"
Stopped = "stopped"
Failed = "failed"
Updating = "updating"
Provisioning = "provisioning"
Bridge = "bridge"
Vxlan = "vxlan"
Physical = "physical"
Lvm = "lvm"
)
================================================
FILE: vm/sort.go
================================================
package vm
type SortDisks []*Disk
func (d SortDisks) Len() int {
return len(d)
}
func (d SortDisks) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d SortDisks) Less(i, j int) bool {
return d[i].Index < d[j].Index
}
================================================
FILE: vm/utils.go
================================================
package vm
import (
"bytes"
"crypto/md5"
"encoding/base32"
"fmt"
"strings"
"github.com/pritunl/mongo-go-driver/v2/bson"
)
func GetMacAddr(id bson.ObjectID, secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "00:" + macBuf.String()
}
func GetMacAddrExternal(id bson.ObjectID,
secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "02:" + macBuf.String()
}
func GetMacAddrExternal6(id bson.ObjectID,
secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "08:" + macBuf.String()
}
func GetMacAddrInternal(id bson.ObjectID,
secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "04:" + macBuf.String()
}
func GetMacAddrHost(id bson.ObjectID,
secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "06:" + macBuf.String()
}
func GetMacAddrNodePort(id bson.ObjectID,
secondId bson.ObjectID) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hash.Write([]byte(secondId.Hex()))
macHash := fmt.Sprintf("%x", hash.Sum(nil))
macHash = macHash[:10]
macBuf := bytes.Buffer{}
for i, run := range macHash {
macBuf.WriteRune(run)
if i%2 == 1 && i != len(macHash)-1 {
macBuf.WriteRune(':')
}
}
return "0a:" + macBuf.String()
}
func GetIface(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("p%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceVirt(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("v%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceExternal(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("e%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceNodeExternal(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("r%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceInternal(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("i%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceNodeInternal(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("j%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceHost(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("h%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceNodePort(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("m%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceCloud(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("o%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceCloudVirt(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("t%s%d", strings.ToLower(hashSum), n)
}
func GetIfaceVlan(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("x%s%d", strings.ToLower(hashSum), n)
}
func GetNamespace(id bson.ObjectID, n int) string {
hash := md5.New()
hash.Write([]byte(id.Hex()))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("n%s%d", strings.ToLower(hashSum), n)
}
func GetHostVxlanIface(parentIface string) string {
hash := md5.New()
hash.Write([]byte(parentIface))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("k%s0", strings.ToLower(hashSum))
}
func GetHostBridgeIface(parentIface string) string {
hash := md5.New()
hash.Write([]byte(parentIface))
hashSum := base32.StdEncoding.EncodeToString(hash.Sum(nil))[:12]
return fmt.Sprintf("b%s0", strings.ToLower(hashSum))
}
================================================
FILE: vm/vm.go
================================================
package vm
import (
"fmt"
"path"
"strings"
"time"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/usb"
"github.com/pritunl/pritunl-cloud/utils"
)
type VirtualMachine struct {
Id bson.ObjectID `json:"id"`
Organization bson.ObjectID `json:"organization"`
UnixId int `json:"unix_id"`
State string `json:"state"`
Timestamp time.Time `json:"timestamp"`
QemuVersion string `json:"qemu_version"`
DiskType string `json:"disk_type"`
DiskPool bson.ObjectID `json:"disk_pool"`
Image bson.ObjectID `json:"image"`
Processors int `json:"processors"`
Memory int `json:"memory"`
Hugepages bool `json:"hugepages"`
Vnc bool `json:"vnc"`
VncDisplay int `json:"vnc_display"`
Spice bool `json:"spice"`
SpicePort int `json:"spice_port"`
Gui bool `json:"gui"`
Disks []*Disk `json:"disks"`
DisksAvailable bool `json:"-"`
NetworkAdapters []*NetworkAdapter `json:"network_adapters"`
CloudSubnet string `json:"cloud_subnet"`
CloudVnic string `json:"cloud_vnic"`
CloudVnicAttach string `json:"cloud_vnic_attach"`
CloudPrivateIp string `json:"cloud_private_ip"`
CloudPublicIp string `json:"cloud_public_ip"`
CloudPublicIp6 string `json:"cloud_public_ip6"`
DhcpIp string `json:"dhcp_ip"`
DhcpIp6 string `json:"dhcp_ip6"`
Uefi bool `json:"uefi"`
SecureBoot bool `json:"secure_boot"`
Tpm bool `json:"tpm"`
DhcpServer bool `json:"dhcp_server"`
Deployment bson.ObjectID `json:"deployment"`
CloudType string `json:"cloud_type"`
SystemKind string `json:"system_kind"`
NoPublicAddress bool `json:"no_public_address"`
NoPublicAddress6 bool `json:"no_public_address6"`
NoHostAddress bool `json:"no_host_address"`
Isos []*Iso `json:"isos"`
UsbDevices []*UsbDevice `json:"usb_devices"`
UsbDevicesAvailable bool `json:"-"`
PciDevices []*PciDevice `json:"pci_devices"`
DriveDevices []*DriveDevice `json:"drive_devices"`
IscsiDevices []*IscsiDevice `json:"iscsi_devices"`
Mounts []*Mount `json:"mounts"`
ImdsVersion int `json:"imds_version"`
ImdsClientSecret string `json:"-"`
ImdsDhcpSecret string `json:"imds_dhcp_secret"`
ImdsHostSecret string `json:"imds_host_secret"`
}
func (v *VirtualMachine) HasExternalNetwork() bool {
return v.Vnc || v.Spice || (v.IscsiDevices != nil &&
len(v.IscsiDevices) > 0)
}
func (v *VirtualMachine) ProtectHome() bool {
return !v.Gui
}
func (v *VirtualMachine) ProtectTmp() bool {
return !v.Gui
}
func (v *VirtualMachine) Running() bool {
return v.State == Starting || v.State == Running
}
func (v *VirtualMachine) GenerateImdsSecret() (err error) {
v.ImdsVersion = 1
v.ImdsClientSecret, err = utils.RandStr(32)
if err != nil {
return
}
v.ImdsDhcpSecret, err = utils.RandStr(32)
if err != nil {
return
}
v.ImdsHostSecret, err = utils.RandStr(32)
if err != nil {
return
}
return
}
type Disk struct {
Id bson.ObjectID `json:"id"`
Index int `json:"index"`
Path string `json:"path"`
}
type Iso struct {
Name string `json:"name"`
}
type UsbDevice struct {
Id string `json:"id"`
Vendor string `json:"vendor"`
Product string `json:"product"`
Bus string `json:"bus"`
Address string `json:"address"`
}
func (u *UsbDevice) Key() string {
return fmt.Sprintf("%s_%s_%s_%s",
u.Bus,
u.Address,
u.Vendor,
u.Product,
)
}
func (u *UsbDevice) Copy() (device *UsbDevice) {
device = &UsbDevice{
Id: u.Id,
Vendor: u.Vendor,
Product: u.Product,
Bus: u.Bus,
Address: u.Address,
}
return
}
func (u *UsbDevice) GetQemuId() string {
return fmt.Sprintf("usb_%s_%s_%s_%s_%d",
u.Bus,
u.Address,
u.Vendor,
u.Product,
utils.RandInt(1111, 9999),
)
}
func (u *UsbDevice) GetDevice() (device *usb.Device, err error) {
device, err = usb.GetDevice(u.Bus, u.Address, u.Vendor, u.Product)
if err != nil {
return
}
return
}
type PciDevice struct {
Slot string `json:"slot"`
}
type DriveDevice struct {
Id string `json:"id"`
Type string `json:"type"`
VgName string `json:"vg_name"`
LvName string `json:"lv_name"`
}
type IscsiDevice struct {
Uri string `json:"iscsi"`
}
type Mount struct {
Name string `json:"name"`
Type string `json:"type"`
Path string `json:"path"`
HostPath string `json:"host_path"`
}
func (d *Disk) GetId() bson.ObjectID {
idStr := strings.Split(path.Base(d.Path), ".")[0]
objId, err := bson.ObjectIDFromHex(idStr)
if err != nil {
return bson.NilObjectID
}
return objId
}
func (d *Disk) Copy() (dsk *Disk) {
dsk = &Disk{
Id: d.Id,
Index: d.Index,
Path: d.Path,
}
return
}
type NetworkAdapter struct {
Type string `json:"type"`
MacAddress string `json:"mac_address"`
Vpc bson.ObjectID `json:"vpc"`
Subnet bson.ObjectID `json:"subnet"`
IpAddress string `json:"ip_address,omitempty"`
IpAddress6 string `json:"ip_address6,omitempty"`
}
func (v *VirtualMachine) Commit(db *database.Database) (err error) {
coll := db.Instances()
addrs := []string{}
addrs6 := []string{}
for _, adapter := range v.NetworkAdapters {
if adapter.IpAddress != "" {
addrs = append(addrs, adapter.IpAddress)
}
if adapter.IpAddress6 != "" {
addrs6 = append(addrs6, adapter.IpAddress6)
}
}
data := bson.M{
"state": v.State,
"timestamp": v.Timestamp,
"public_ips": addrs,
}
if v.State == Running || len(addrs6) > 0 {
data["public_ips6"] = addrs6
}
if v.QemuVersion != "" {
data["qemu_version"] = v.QemuVersion
}
err = coll.UpdateId(v.Id, &bson.M{
"$set": data,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if !v.Deployment.IsZero() {
coll = db.Deployments()
fields := bson.M{
"instance_data.public_ips": addrs,
}
if v.State == Running || len(addrs6) > 0 {
fields["instance_data.public_ips6"] = addrs6
}
err = coll.UpdateId(v.Deployment, &bson.M{
"$set": fields,
})
if err != nil {
err = database.ParseError(err)
return
}
}
return
}
func (v *VirtualMachine) CommitCloudVnic(db *database.Database) (err error) {
coll := db.Instances()
err = coll.UpdateId(v.Id, &bson.M{
"$set": &bson.M{
"cloud_vnic": v.CloudVnic,
"cloud_vnic_attach": v.CloudVnicAttach,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (v *VirtualMachine) CommitCloudIps(db *database.Database) (err error) {
coll := db.Instances()
cloudPivateAddrs := []string{}
if v.CloudPrivateIp != "" {
cloudPivateAddrs = append(cloudPivateAddrs, v.CloudPrivateIp)
}
cloudPublicAddrs := []string{}
if v.CloudPublicIp != "" {
cloudPublicAddrs = append(cloudPublicAddrs, v.CloudPublicIp)
}
cloudPublicAddrs6 := []string{}
if v.CloudPublicIp6 != "" {
cloudPublicAddrs6 = append(cloudPublicAddrs6, v.CloudPublicIp6)
}
err = coll.UpdateId(v.Id, &bson.M{
"$set": &bson.M{
"cloud_private_ips": cloudPivateAddrs,
"cloud_public_ips": cloudPublicAddrs,
"cloud_public_ips6": cloudPublicAddrs6,
},
})
if err != nil {
err = database.ParseError(err)
return
}
if !v.Deployment.IsZero() {
coll = db.Deployments()
err = coll.UpdateId(v.Deployment, &bson.M{
"$set": &bson.M{
"instance_data.cloud_private_ips": cloudPivateAddrs,
"instance_data.cloud_public_ips": cloudPublicAddrs,
"instance_data.cloud_public_ips6": cloudPublicAddrs6,
},
})
if err != nil {
err = database.ParseError(err)
return
}
}
return
}
func (v *VirtualMachine) CommitState(db *database.Database, action string) (
err error) {
coll := db.Instances()
addrs := []string{}
addrs6 := []string{}
for _, adapter := range v.NetworkAdapters {
if adapter.IpAddress != "" {
addrs = append(addrs, adapter.IpAddress)
}
if adapter.IpAddress6 != "" {
addrs6 = append(addrs6, adapter.IpAddress6)
}
}
data := bson.M{
"action": action,
"state": v.State,
"timestamp": v.Timestamp,
"public_ips": addrs,
"public_ips6": addrs6,
}
if v.QemuVersion != "" {
data["qemu_version"] = v.QemuVersion
}
err = coll.UpdateId(v.Id, &bson.M{
"$set": data,
})
if err != nil {
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
return
}
================================================
FILE: vmdk/utils.go
================================================
package vmdk
import (
"bytes"
"os"
"github.com/dropbox/godropbox/errors"
"github.com/google/uuid"
"github.com/pritunl/pritunl-cloud/errortypes"
)
func SetRandUuid(diskPath string) (err error) {
diskUuid := uuid.New()
diskFile, err := os.OpenFile(diskPath, os.O_RDWR, 0)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to open file"),
}
return
}
defer func() {
err = diskFile.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to write file"),
}
return
}
}()
buffer := make([]byte, 10000)
nRead, err := diskFile.Read(buffer)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to read file"),
}
return
}
i := bytes.Index(buffer, []byte("ddb.uuid.image="))
newBuffer := append(buffer[:i+16], []byte(diskUuid.String())...)
newBuffer = append(newBuffer, buffer[i+52:]...)
nWrite, err := diskFile.WriteAt(newBuffer, 0)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to write file"),
}
return
}
if nRead != nWrite {
err = &errortypes.WriteError{
errors.New("vmdk: Write count mismatch"),
}
return
}
err = diskFile.Sync()
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to sync file"),
}
return
}
return
}
func SetUuid(diskPath string, diskUuid string) (err error) {
diskFile, err := os.OpenFile(diskPath, os.O_RDWR, 0)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to open file"),
}
return
}
defer func() {
err = diskFile.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to write file"),
}
return
}
}()
buffer := make([]byte, 10000)
nRead, err := diskFile.Read(buffer)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to read file"),
}
return
}
i := bytes.Index(buffer, []byte("ddb.uuid.image="))
newBuffer := append(buffer[:i+16], []byte(diskUuid)...)
newBuffer = append(newBuffer, buffer[i+52:]...)
nWrite, err := diskFile.WriteAt(newBuffer, 0)
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to write file"),
}
return
}
if nRead != nWrite {
err = &errortypes.WriteError{
errors.New("vmdk: Write count mismatch"),
}
return
}
err = diskFile.Sync()
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to sync file"),
}
return
}
return
}
func GetUuid(diskPath string) (diskUuid string, err error) {
diskFile, err := os.Open(diskPath)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to open file"),
}
return
}
defer func() {
err = diskFile.Close()
if err != nil {
err = &errortypes.WriteError{
errors.Wrap(err, "vmdk: Failed to write file"),
}
return
}
}()
buffer := make([]byte, 10000)
_, err = diskFile.Read(buffer)
if err != nil {
err = &errortypes.ReadError{
errors.Wrap(err, "vmdk: Failed to read file"),
}
return
}
i := bytes.Index(buffer, []byte("ddb.uuid.image="))
diskUuid = string(buffer[i+16 : i+52])
return
}
================================================
FILE: vpc/constants.go
================================================
package vpc
const (
Destination = "destination"
)
================================================
FILE: vpc/ip.go
================================================
package vpc
import (
"net"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/utils"
)
type VpcIp struct {
Id bson.ObjectID `bson:"_id,omitempty"`
Vpc bson.ObjectID `bson:"vpc"`
Subnet bson.ObjectID `bson:"subnet"`
Ip int64 `bson:"ip"`
Instance bson.ObjectID `bson:"instance"`
}
func (i *VpcIp) GetIp() net.IP {
return utils.Int2IpAddress(i.Ip * 2)
}
func (i *VpcIp) GetIps() (net.IP, net.IP) {
return utils.IpIndex2Ip(i.Ip)
}
================================================
FILE: vpc/subnet.go
================================================
package vpc
import (
"net"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/utils"
)
type Subnet struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Network string `bson:"network" json:"network"`
}
func (s *Subnet) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
s.Name = utils.FilterName(s.Name)
return
}
func (s *Subnet) GetNetwork() (network *net.IPNet, err error) {
_, network, err = net.ParseCIDR(s.Network)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "vpc: Failed to parse subnet"),
}
return
}
return
}
func (s *Subnet) GetIndexRange() (start, stop int64, err error) {
network, err := s.GetNetwork()
if err != nil {
return
}
start, err = utils.GetFirstIpIndex(network)
if err != nil {
return
}
stop, err = utils.GetLastIpIndex(network)
if err != nil {
return
}
return
}
================================================
FILE: vpc/utils.go
================================================
package vpc
import (
"bytes"
"crypto/md5"
"fmt"
"net"
"github.com/dropbox/godropbox/container/set"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/utils"
)
func GetIp6(vpcId, instId bson.ObjectID) net.IP {
netHash := md5.New()
netHash.Write(vpcId[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
instHash := md5.New()
instHash.Write(instId[:])
instHashSum := "0" + fmt.Sprintf("%x", instHash.Sum(nil))[:15]
ip := fmt.Sprintf("fd97%s%s", netHashSum, instHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
return net.ParseIP(ipBuf.String())
}
func GetGatewayIp6(vpcId, instId bson.ObjectID) net.IP {
netHash := md5.New()
netHash.Write(vpcId[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
instHash := md5.New()
instHash.Write([]byte("gateway"))
instHash.Write(instId[:])
instHashSum := "0" + fmt.Sprintf("%x", instHash.Sum(nil))[:15]
ip := fmt.Sprintf("fd97%s%s", netHashSum, instHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
return net.ParseIP(ipBuf.String())
}
func GetLinkIp6(vpcId, instId bson.ObjectID) net.IP {
netHash := md5.New()
netHash.Write(vpcId[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
instHash := md5.New()
instHash.Write(instId[:])
instHashSum := "0" + fmt.Sprintf("%x", instHash.Sum(nil))[:15]
ip := fmt.Sprintf("fd97%s%s", netHashSum, instHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
return net.ParseIP(ipBuf.String())
}
func GetGatewayLinkIp6(vpcId, instId bson.ObjectID) net.IP {
netHash := md5.New()
netHash.Write(vpcId[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
instHash := md5.New()
instHash.Write([]byte("gateway"))
instHash.Write(instId[:])
instHashSum := "0" + fmt.Sprintf("%x", instHash.Sum(nil))[:15]
ip := fmt.Sprintf("fd97%s%s", netHashSum, instHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
return net.ParseIP(ipBuf.String())
}
func Get(db *database.Database, vcId bson.ObjectID) (
vc *Vpc, err error) {
coll := db.Vpcs()
vc = &Vpc{}
err = coll.FindOneId(vcId, vc)
if err != nil {
return
}
return
}
func GetOrg(db *database.Database, orgId, vcId bson.ObjectID) (
vc *Vpc, err error) {
coll := db.Vpcs()
vc = &Vpc{}
err = coll.FindOne(db, &bson.M{
"_id": vcId,
"organization": orgId,
}).Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func ExistsOrg(db *database.Database, orgId, vcId bson.ObjectID) (
exists bool, err error) {
coll := db.Vpcs()
n, err := coll.CountDocuments(
db,
&bson.M{
"_id": vcId,
"organization": orgId,
},
)
if err != nil {
return
}
if n > 0 {
exists = true
}
return
}
func GetAll(db *database.Database, query *bson.M) (
vcs []*Vpc, err error) {
coll := db.Vpcs()
vcs = []*Vpc{}
cursor, err := coll.Find(
db,
query,
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &Vpc{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vcs = append(vcs, vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetOne(db *database.Database, query *bson.M) (vc *Vpc, err error) {
coll := db.Vpcs()
vc = &Vpc{}
err = coll.FindOne(db, query).Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllNames(db *database.Database, query *bson.M) (
vpcs []*Vpc, err error) {
coll := db.Vpcs()
vpcs = []*Vpc{}
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetProjection(bson.D{
{"name", 1},
{"organization", 1},
{"datacenter", 1},
{"type", 1},
{"subnets", 1},
}),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &Vpc{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vpcs = append(vpcs, vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetAllPaged(db *database.Database, query *bson.M,
page, pageCount int64) (vcs []*Vpc, count int64, err error) {
coll := db.Vpcs()
vcs = []*Vpc{}
if len(*query) == 0 {
count, err = coll.EstimatedDocumentCount(db)
if err != nil {
err = database.ParseError(err)
return
}
} else {
count, err = coll.CountDocuments(db, query)
if err != nil {
err = database.ParseError(err)
return
}
}
if pageCount == 0 {
pageCount = 20
}
maxPage := count / pageCount
if count == pageCount {
maxPage = 0
}
page = utils.Min64(page, maxPage)
skip := utils.Min64(page*pageCount, count)
cursor, err := coll.Find(
db,
query,
options.Find().
SetSort(bson.D{{"name", 1}}).
SetSkip(skip).
SetLimit(pageCount),
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &Vpc{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vcs = append(vcs, vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetIds(db *database.Database, ids []bson.ObjectID) (
vcs []*Vpc, err error) {
coll := db.Vpcs()
vcs = []*Vpc{}
cursor, err := coll.Find(
db,
&bson.M{
"_id": &bson.M{
"$in": ids,
},
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &Vpc{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vcs = append(vcs, vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetDatacenter(db *database.Database, dcId bson.ObjectID) (
vcs []*Vpc, err error) {
coll := db.Vpcs()
vcs = []*Vpc{}
cursor, err := coll.Find(
db,
&bson.M{
"datacenter": dcId,
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &Vpc{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vcs = append(vcs, vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func DistinctIds(db *database.Database, matchIds []bson.ObjectID) (
idsSet set.Set, err error) {
coll := db.Images()
ids := []bson.ObjectID{}
idsSet = set.NewSet()
err = coll.Distinct(
db,
"_id",
&bson.M{
"_id": &bson.M{
"$in": matchIds,
},
},
).Decode(&ids)
if err != nil {
err = database.ParseError(err)
return
}
for _, id := range ids {
idsSet.Add(id)
}
return
}
func Remove(db *database.Database, vcId bson.ObjectID) (err error) {
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"vpc": vcId,
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Vpcs()
_, err = coll.DeleteOne(db, &bson.M{
"_id": vcId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveOrg(db *database.Database, orgId, vcId bson.ObjectID) (
err error) {
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"vpc": vcId,
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Vpcs()
_, err = coll.DeleteOne(db, &bson.M{
"organization": orgId,
"_id": vcId,
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveMulti(db *database.Database, vcIds []bson.ObjectID) (err error) {
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"vpc": &bson.M{
"$in": vcIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Vpcs()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": vcIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveMultiOrg(db *database.Database, orgId bson.ObjectID,
vcIds []bson.ObjectID) (err error) {
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"vpc": &bson.M{
"$in": vcIds,
},
"organization": orgId,
})
if err != nil {
err = database.ParseError(err)
return
}
coll = db.Vpcs()
_, err = coll.DeleteMany(db, &bson.M{
"_id": &bson.M{
"$in": vcIds,
},
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func GetIpsMapped(db *database.Database, ids []bson.ObjectID) (
vpcsMap map[bson.ObjectID][]*VpcIp, err error) {
coll := db.VpcsIp()
vpcsMap = map[bson.ObjectID][]*VpcIp{}
cursor, err := coll.Find(
db,
&bson.M{
"vpc": &bson.M{
"$in": ids,
},
},
)
if err != nil {
err = database.ParseError(err)
return
}
defer cursor.Close(db)
for cursor.Next(db) {
vc := &VpcIp{}
err = cursor.Decode(vc)
if err != nil {
err = database.ParseError(err)
return
}
vpcsMap[vc.Vpc] = append(vpcsMap[vc.Vpc], vc)
}
err = cursor.Err()
if err != nil {
err = database.ParseError(err)
return
}
return
}
func RemoveInstanceIps(db *database.Database, instId bson.ObjectID) (
err error) {
coll := db.VpcsIp()
_, err = coll.UpdateMany(db, &bson.M{
"instance": instId,
}, &bson.M{
"$set": &bson.M{
"instance": nil,
},
})
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
func RemoveInstanceIp(db *database.Database, instId,
vpcId bson.ObjectID) (err error) {
coll := db.VpcsIp()
_, err = coll.UpdateOne(
db,
&bson.M{
"vpc": vpcId,
"instance": instId,
},
&bson.M{
"$set": &bson.M{
"instance": nil,
},
},
)
if err != nil {
err = database.ParseError(err)
switch err.(type) {
case *database.NotFoundError:
err = nil
default:
return
}
}
return
}
================================================
FILE: vpc/vpc.go
================================================
package vpc
import (
"bytes"
"crypto/md5"
"fmt"
"math/rand/v2"
"net"
"strconv"
"strings"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/mongo-go-driver/v2/bson"
"github.com/pritunl/mongo-go-driver/v2/mongo/options"
"github.com/pritunl/pritunl-cloud/database"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/requires"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/utils"
)
type Route struct {
Destination string `bson:"destination" json:"destination"`
Target string `bson:"target" json:"target"`
}
type Map struct {
Type string `bson:"type" json:"type"`
Destination string `bson:"destination" json:"destination"`
Target string `bson:"target" json:"target"`
}
type Arp struct {
Ip string `bson:"ip" json:"ip"`
Mac string `bson:"mac" json:"mac"`
}
type Vpc struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Comment string `bson:"comment" json:"comment"`
VpcId int `bson:"vpc_id" json:"vpc_id"`
Network string `bson:"network" json:"network"`
Network6 string `bson:"-" json:"network6"`
Subnets []*Subnet `bson:"subnets" json:"subnets"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
IcmpRedirects bool `bson:"icmp_redirects" json:"icmp_redirects"`
Routes []*Route `bson:"routes" json:"routes"`
Maps []*Map `bson:"maps" json:"maps"`
Arps []*Arp `bson:"arps" json:"arps"`
DeleteProtection bool `bson:"delete_protection" json:"delete_protection"`
curSubnets []*Subnet `bson:"-" json:"-"`
}
type Completion struct {
Id bson.ObjectID `bson:"_id,omitempty" json:"id"`
Name string `bson:"name" json:"name"`
Organization bson.ObjectID `bson:"organization" json:"organization"`
VpcId int `bson:"vpc_id" json:"vpc_id"`
Network string `bson:"network" json:"network"`
Subnets []*Subnet `bson:"subnets" json:"subnets"`
Datacenter bson.ObjectID `bson:"datacenter" json:"datacenter"`
}
func (v *Vpc) Validate(db *database.Database) (
errData *errortypes.ErrorData, err error) {
v.Name = utils.FilterName(v.Name)
if v.Organization.IsZero() {
errData = &errortypes.ErrorData{
Error: "organization_required",
Message: "Missing required organization",
}
return
}
if v.Datacenter.IsZero() {
errData = &errortypes.ErrorData{
Error: "datacenter_required",
Message: "Missing required datacenter",
}
return
}
network, e := v.GetNetwork()
if e != nil {
errData = &errortypes.ErrorData{
Error: "network_invalid",
Message: "Network address invalid",
}
return
}
network6, e := v.GetNetwork6()
if e != nil {
errData = &errortypes.ErrorData{
Error: "network_invalid6",
Message: "IPv6 network address invalid",
}
return
}
v.Network = network.String()
if v.Subnets == nil {
v.Subnets = []*Subnet{}
}
subnetRanges := []struct {
Id bson.ObjectID
Start int64
Stop int64
}{}
subs := []*Subnet{}
for _, sub := range v.Subnets {
errData, err = sub.Validate(db)
if err != nil {
return
}
if errData != nil {
return
}
if sub.Network == "" {
continue
}
subNetwork, e := sub.GetNetwork()
if e != nil {
errData = &errortypes.ErrorData{
Error: "subnet_network_invalid",
Message: "Subnet network address invalid",
}
return
}
cidr, _ := subNetwork.Mask.Size()
if cidr < 8 {
errData = &errortypes.ErrorData{
Error: "subnet_network_size_invalid",
Message: "Subnet network size too big",
}
return
}
if cidr > 28 {
errData = &errortypes.ErrorData{
Error: "subnet_network_size_invalid",
Message: "Subnet network size too small",
}
return
}
sub.Network = subNetwork.String()
if !utils.NetworkContains(network, subNetwork) {
errData = &errortypes.ErrorData{
Error: "subnet_network_range_invalid",
Message: "Subnet network outside of VPC network",
}
return
}
subStart, subStop, e := sub.GetIndexRange()
if e != nil {
err = e
return
}
subnetRanges = append(subnetRanges, struct {
Id bson.ObjectID
Start int64
Stop int64
}{
Id: sub.Id,
Start: subStart,
Stop: subStop,
})
subs = append(subs, sub)
}
v.Subnets = subs
for _, sub := range v.Subnets {
subStart, subStop, e := sub.GetIndexRange()
if e != nil {
err = e
return
}
for _, s := range subnetRanges {
if s.Id == sub.Id {
continue
}
if (subStart >= s.Start && subStart <= s.Stop) ||
(subStop >= s.Start && subStop <= s.Stop) {
errData = &errortypes.ErrorData{
Error: "subnet_network_range_overlap",
Message: "VPC cannot have overlapping subnets",
}
return
}
}
}
if v.Routes == nil {
v.Routes = []*Route{}
}
destinations := set.NewSet()
for _, route := range v.Routes {
if destinations.Contains(route.Destination) {
errData = &errortypes.ErrorData{
Error: "route_duplicate_destination",
Message: "Duplicate mp destinations",
}
return
}
destinations.Add(route.Destination)
if strings.Contains(route.Destination, ":") !=
strings.Contains(route.Target, ":") {
errData = &errortypes.ErrorData{
Error: "route_target_destination_invalid",
Message: "Route target/destination invalid",
}
return
}
_, destination, e := net.ParseCIDR(route.Destination)
if e != nil {
errData = &errortypes.ErrorData{
Error: "route_destination_invalid",
Message: "Route destination invalid",
}
return
}
route.Destination = destination.String()
if route.Destination == "0.0.0.0/0" || route.Destination == "::/0" {
errData = &errortypes.ErrorData{
Error: "route_destination_invalid",
Message: "Route destination invalid",
}
return
}
target := net.ParseIP(route.Target)
if target == nil {
errData = &errortypes.ErrorData{
Error: "route_target_invalid",
Message: "Route target invalid",
}
return
}
route.Target = target.String()
if route.Target == "0.0.0.0" {
errData = &errortypes.ErrorData{
Error: "route_target_invalid",
Message: "Route target invalid",
}
return
}
if !strings.Contains(route.Target, ":") {
if !network.Contains(target) {
errData = &errortypes.ErrorData{
Error: "route_target_invalid_network",
Message: "Route target not in VPC network",
}
return
}
} else {
if !network6.Contains(target) {
errData = &errortypes.ErrorData{
Error: "route_target_invalid_network6",
Message: "Route target not in VPC IPv6 network",
}
return
}
}
}
maps := []*Map{}
destinations = set.NewSet()
for _, mp := range v.Maps {
if mp.Target == "" && mp.Destination == "" {
continue
}
if mp.Type == "" {
mp.Type = Destination
}
if mp.Type != Destination {
errData = &errortypes.ErrorData{
Error: "map_invalid_type",
Message: "Map type invalid",
}
return
}
_, destination, e := net.ParseCIDR(mp.Destination)
if e != nil {
errData = &errortypes.ErrorData{
Error: "map_destination_invalid",
Message: "Map destination invalid",
}
return
}
mp.Destination = destination.String()
target := net.ParseIP(mp.Target)
if target == nil {
errData = &errortypes.ErrorData{
Error: "map_target_invalid",
Message: "Map target invalid",
}
return
}
mp.Target = target.String()
if destinations.Contains(mp.Destination) {
errData = &errortypes.ErrorData{
Error: "map_duplicate_destination",
Message: "Duplicate map destinations",
}
return
}
destinations.Add(mp.Destination)
if strings.Contains(mp.Destination, ":") !=
strings.Contains(mp.Target, ":") {
errData = &errortypes.ErrorData{
Error: "map_target_destination_invalid",
Message: "Map target/destination invalid",
}
return
}
if mp.Destination == "0.0.0.0/0" || mp.Destination == "::/0" {
errData = &errortypes.ErrorData{
Error: "map_destination_invalid",
Message: "Map destination invalid",
}
return
}
if mp.Target == "0.0.0.0" {
errData = &errortypes.ErrorData{
Error: "map_target_invalid",
Message: "Map target invalid",
}
return
}
if !strings.Contains(mp.Target, ":") {
if !network.Contains(target) {
errData = &errortypes.ErrorData{
Error: "map_target_invalid_network",
Message: "Map target not in VPC network",
}
return
}
} else {
if !network6.Contains(target) {
errData = &errortypes.ErrorData{
Error: "map_target_invalid_network6",
Message: "Map target not in VPC IPv6 network",
}
return
}
}
maps = append(maps, mp)
}
v.Maps = maps
arps := []*Arp{}
ips := set.NewSet()
for _, ap := range v.Arps {
if ap.Ip == "" && ap.Mac == "" {
continue
}
arpIp := net.ParseIP(ap.Ip)
if arpIp == nil {
errData = &errortypes.ErrorData{
Error: "arp_ip_invalid",
Message: "Arp IP invalid",
}
return
}
ap.Ip = arpIp.String()
if ips.Contains(ap.Ip) {
errData = &errortypes.ErrorData{
Error: "arp_duplicate_destination",
Message: "Duplicate arp destinations",
}
return
}
ips.Add(ap.Ip)
arpMac, e := net.ParseMAC(ap.Mac)
if e != nil {
errData = &errortypes.ErrorData{
Error: "arp_mac_invalid",
Message: "Arp mac invalid",
}
return
}
ap.Mac = arpMac.String()
if !strings.Contains(ap.Ip, ":") {
if !network.Contains(arpIp) {
errData = &errortypes.ErrorData{
Error: "arp_ip_subnet_invalid",
Message: "ARP IP outside of VPC network",
}
return
}
} else {
if !network6.Contains(arpIp) {
errData = &errortypes.ErrorData{
Error: "arp_ip6_subnet_invalid",
Message: "ARP IP outside of VPC network",
}
return
}
}
arps = append(arps, ap)
}
v.Arps = arps
return
}
func (v *Vpc) PreCommit() {
if v.Subnets == nil {
v.curSubnets = []*Subnet{}
} else {
v.curSubnets = v.Subnets
}
}
func (v *Vpc) PostCommit(db *database.Database) (
errData *errortypes.ErrorData, err error) {
curSubnets := map[bson.ObjectID]*Subnet{}
for _, sub := range v.curSubnets {
curSubnets[sub.Id] = sub
}
newIds := set.NewSet()
for _, sub := range v.Subnets {
newIds.Add(sub.Id)
curSub := curSubnets[sub.Id]
if !sub.Id.IsZero() && curSub != nil {
if curSub.Network != sub.Network {
errData = &errortypes.ErrorData{
Error: "subnet_network_modified",
Message: "Cannot modify VPC subnet",
}
return
}
} else {
sub.Id = bson.NewObjectID()
for _, s := range v.curSubnets {
if s.Network == sub.Network {
sub.Id = s.Id
}
}
}
}
for _, sub := range v.curSubnets {
if !newIds.Contains(sub.Id) {
err = v.RemoveSubnet(db, sub.Id)
if err != nil {
return
}
}
}
return
}
func (v *Vpc) Json() {
netHash := md5.New()
netHash.Write(v.Id[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
ip := fmt.Sprintf("fd97%s", netHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
v.Network6 = ipBuf.String() + "::/64"
}
func (v *Vpc) GetSubnet(id bson.ObjectID) (sub *Subnet) {
if v.Subnets == nil || id.IsZero() {
return
}
for _, s := range v.Subnets {
if s.Id == id {
sub = s
return
}
}
return
}
func (v *Vpc) GetSubnetName(name string) (sub *Subnet) {
if v.Subnets == nil || name == "" {
return
}
for _, s := range v.Subnets {
if s.Name == name {
sub = s
return
}
}
return
}
func (v *Vpc) GetNetwork() (network *net.IPNet, err error) {
_, network, err = net.ParseCIDR(v.Network)
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "vpc: Failed to parse network"),
}
return
}
return
}
func (v *Vpc) GetNetwork6() (network *net.IPNet, err error) {
netHash := md5.New()
netHash.Write(v.Id[:])
netHashSum := fmt.Sprintf("%x", netHash.Sum(nil))[:12]
ip := fmt.Sprintf("fd97%s", netHashSum)
ipBuf := bytes.Buffer{}
for i, run := range ip {
if i%4 == 0 && i != 0 && i != len(ip)-1 {
ipBuf.WriteRune(':')
}
ipBuf.WriteRune(run)
}
_, network, err = net.ParseCIDR(ipBuf.String() + "::/64")
if err != nil {
err = &errortypes.ParseError{
errors.Wrap(err, "vpc: Failed to parse network"),
}
return
}
return
}
func (v *Vpc) InitVpc() {
if v.Subnets != nil {
for _, sub := range v.Subnets {
sub.Id = bson.NewObjectID()
}
}
}
func (v *Vpc) GetGateway() (ip net.IP, err error) {
network, err := v.GetNetwork()
if err != nil {
return
}
ip = network.IP
utils.IncIpAddress(ip)
return
}
func (v *Vpc) GetGateway6() (ip net.IP, err error) {
network, err := v.GetNetwork6()
if err != nil {
return
}
ip = network.IP
utils.IncIpAddress(ip)
return
}
func (v *Vpc) GetIp(db *database.Database,
subId, instId bson.ObjectID) (instIp, gateIp net.IP, err error) {
subnet := v.GetSubnet(subId)
if subnet == nil {
err = &errortypes.ReadError{
errors.New("vpc: Subnet does not exist"),
}
return
}
coll := db.VpcsIp()
vpcIp := &VpcIp{}
err = coll.FindOne(db, &bson.M{
"vpc": v.Id,
"instance": instId,
}).Decode(vpcIp)
if err != nil {
err = database.ParseError(err)
vpcIp = nil
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
if vpcIp == nil {
vpcIp = &VpcIp{}
opts := options.FindOneAndUpdate().
SetReturnDocument(options.After)
err = coll.FindOneAndUpdate(
db,
&bson.M{
"vpc": v.Id,
"subnet": subId,
"instance": nil,
},
&bson.M{
"$set": &bson.M{
"instance": instId,
},
},
opts,
).Decode(vpcIp)
if err != nil {
err = database.ParseError(err)
vpcIp = nil
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
}
if vpcIp == nil {
vpcIp = &VpcIp{}
err = coll.FindOne(
db,
&bson.M{
"vpc": v.Id,
"subnet": subId,
},
options.FindOne().
SetSort(bson.D{{"ip", -1}}),
).Decode(vpcIp)
if err != nil {
vpcIp = nil
err = database.ParseError(err)
if _, ok := err.(*database.NotFoundError); ok {
err = nil
} else {
return
}
}
start, stop, e := subnet.GetIndexRange()
if e != nil {
err = e
return
}
curIp := start
if vpcIp != nil {
start = vpcIp.Ip + 1
}
for {
if curIp > stop {
err = &errortypes.NotFoundError{
errors.New("vpc: Address pool full"),
}
return
}
vpcIp = &VpcIp{
Vpc: v.Id,
Subnet: subId,
Ip: curIp,
Instance: instId,
}
_, err = coll.InsertOne(db, vpcIp)
if err != nil {
vpcIp = nil
err = database.ParseError(err)
if _, ok := err.(*database.DuplicateKeyError); ok {
err = nil
curIp += 1
continue
}
return
}
break
}
}
instIp, gateIp = vpcIp.GetIps()
gateIp, err = v.GetGateway()
if err != nil {
return
}
return
}
func (v *Vpc) GetIp6(instId bson.ObjectID) net.IP {
return GetIp6(v.Id, instId)
}
func (v *Vpc) GetLinkIp6(instId bson.ObjectID) net.IP {
return GetLinkIp6(v.Id, instId)
}
func (v *Vpc) GetGatewayIp6(instId bson.ObjectID) net.IP {
return GetGatewayIp6(v.Id, instId)
}
func (v *Vpc) GetGatewayLinkIp6(instId bson.ObjectID) net.IP {
return GetGatewayLinkIp6(v.Id, instId)
}
func (v *Vpc) RemoveSubnet(db *database.Database, subId bson.ObjectID) (
err error) {
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"vpc": v.Id,
"subnet": subId,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
func (v *Vpc) Commit(db *database.Database) (err error) {
coll := db.Vpcs()
err = coll.Commit(v.Id, v)
if err != nil {
return
}
return
}
func (v *Vpc) CommitFields(db *database.Database, fields set.Set) (
err error) {
coll := db.Vpcs()
err = coll.CommitFields(v.Id, v, fields)
if err != nil {
return
}
return
}
func (v *Vpc) Insert(db *database.Database) (err error) {
coll := db.Vpcs()
if !v.Id.IsZero() {
err = &errortypes.DatabaseError{
errors.New("vpc: Vpc already exists"),
}
return
}
vpcIds := []int{}
parts := strings.Split(settings.Hypervisor.VlanRanges, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
bounds := strings.Split(part, "-")
if len(bounds) != 2 {
err = &errortypes.ParseError{
errors.New("vpc: Invalid vlan range format"),
}
return
}
start, e := strconv.Atoi(strings.TrimSpace(bounds[0]))
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "vpc: Invalid start vlan"),
}
return
}
end, e := strconv.Atoi(strings.TrimSpace(bounds[1]))
if e != nil {
err = &errortypes.ParseError{
errors.Wrap(e, "vpc: Invalid end vlan"),
}
return
}
if start >= end {
err = &errortypes.ParseError{
errors.New("vpc: Start vlan larger than end vlan"),
}
return
}
for i := start; i <= end; i++ {
vpcIds = append(vpcIds, i)
}
}
rand.Shuffle(len(vpcIds), func(i, j int) {
vpcIds[i], vpcIds[j] = vpcIds[j], vpcIds[i]
})
for _, vpcId := range vpcIds {
v.VpcId = vpcId
resp, e := coll.InsertOne(db, v)
if e != nil {
err = database.ParseError(e)
if _, ok := err.(*database.DuplicateKeyError); ok {
err = nil
continue
}
return
}
v.Id = resp.InsertedID.(bson.ObjectID)
return
}
err = &errortypes.DatabaseError{
errors.New("vpc: No available vlan IDs"),
}
return
}
func init() {
module := requires.New("vpc")
module.After("settings")
module.Handler = func() (err error) {
db := database.GetDatabase()
defer db.Close()
coll := db.VpcsIp()
_, err = coll.DeleteMany(db, &bson.M{
"subnet": nil,
})
if err != nil {
err = database.ParseError(err)
return
}
return
}
}
================================================
FILE: vxlan/vxlan.go
================================================
package vxlan
import (
"strconv"
"strings"
"time"
"github.com/dropbox/godropbox/container/set"
"github.com/dropbox/godropbox/errors"
"github.com/pritunl/pritunl-cloud/errortypes"
"github.com/pritunl/pritunl-cloud/ip"
"github.com/pritunl/pritunl-cloud/iproute"
"github.com/pritunl/pritunl-cloud/node"
"github.com/pritunl/pritunl-cloud/settings"
"github.com/pritunl/pritunl-cloud/state"
"github.com/pritunl/pritunl-cloud/utils"
"github.com/pritunl/pritunl-cloud/vm"
"github.com/sirupsen/logrus"
)
var (
curIfaces set.Set
curDatabase set.Set
curDatabaseIfaces set.Set
)
func initIfaces(stat *state.State, internaIfaces []string) (err error) {
ifaces := set.NewSet()
newCurIfaces := set.NewSet()
for _, iface := range stat.Interfaces() {
ifaces.Add(iface)
if len(iface) == 14 && (strings.HasPrefix(iface, "k") ||
strings.HasPrefix(iface, "b")) {
newCurIfaces.Add(iface)
}
}
parentVxIfaces := map[string]string{}
parentBrIfaces := map[string]string{}
newIfaces := set.NewSet()
for _, iface := range internaIfaces {
vxIface := vm.GetHostVxlanIface(iface)
brIface := vm.GetHostBridgeIface(iface)
parentVxIfaces[vxIface] = iface
parentBrIfaces[brIface] = iface
newIfaces.Add(vxIface)
newIfaces.Add(brIface)
}
remIfaces := newCurIfaces.Copy()
remIfaces.Subtract(newIfaces)
for ifaceInf := range remIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "b") {
logrus.WithFields(logrus.Fields{
"bridge": iface,
}).Info("vxlan: Removing bridge")
_, _ = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
},
"ip", "link",
"set", "dev",
iface, "down",
)
_ = iproute.BridgeDelete("", iface)
}
}
for ifaceInf := range remIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "k") {
logrus.WithFields(logrus.Fields{
"vxlan": iface,
}).Info("vxlan: Removing vxlan")
_, _ = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
},
"ip", "link",
"del", iface,
)
}
}
time.Sleep(200 * time.Millisecond)
newCurIfaces.Intersect(newIfaces)
for ifaceInf := range newCurIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "b") {
parentIface := parentBrIfaces[iface]
if parentIface == "" {
continue
}
vxIface := vm.GetHostVxlanIface(parentIface)
if ifaces.Contains(vxIface) {
_, err = utils.ExecCombinedOutputLogged(
[]string{"does not exist"},
"ip", "link", "set",
vxIface, "master", iface,
)
if err != nil {
return
}
}
}
}
time.Sleep(300 * time.Millisecond)
for ifaceInf := range newCurIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "b") {
parentIface := parentBrIfaces[iface]
if parentIface == "" {
continue
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev",
iface, "up",
)
if err != nil {
return
}
}
}
time.Sleep(500 * time.Millisecond)
for ifaceInf := range newCurIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "k") {
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev",
iface, "up",
)
if err != nil {
return
}
}
}
ip.ClearIfacesCache("")
curIfaces = newCurIfaces
return
}
func initDatabase(stat *state.State, internaIfaces []string) (err error) {
output, err := utils.ExecOutput("", "bridge", "fdb")
if err != nil {
return
}
nodeSelf := stat.Node()
nodeDc := stat.NodeDatacenter()
if nodeDc == nil {
return
}
nodes := stat.Nodes()
if nodes == nil {
nodes = []*node.Node{}
}
newDb := set.NewSet()
for _, nde := range nodes {
if nde.Id == nodeSelf.Id || nde.Datacenter != nodeDc.Id ||
nde.PrivateIps == nil || !nodeDc.Vxlan() {
continue
}
for _, privateIp := range nde.PrivateIps {
newDb.Add(privateIp)
}
}
newCurDb := set.NewSet()
newCurIfaces := set.NewSet()
ifaceBridgeDb := map[string]set.Set{}
for _, line := range strings.Split(output, "\n") {
fields := strings.Fields(line)
if len(fields) != 7 || fields[0] != "00:00:00:00:00:00" {
continue
}
iface := fields[2]
if len(iface) != 14 || !strings.HasPrefix(iface, "k") {
continue
}
dest := fields[4]
bridgeSet := ifaceBridgeDb[iface]
if bridgeSet == nil {
bridgeSet = set.NewSet()
ifaceBridgeDb[iface] = bridgeSet
}
newCurIfaces.Add(iface)
bridgeSet.Add(dest)
newCurDb.Add(dest)
}
for ifaceInf := range newCurIfaces.Iter() {
iface := ifaceInf.(string)
ifaceDb := ifaceBridgeDb[iface]
addDb := newDb.Copy()
addDb.Subtract(ifaceDb)
for destInf := range addDb.Iter() {
dest := destInf.(string)
if dest == "" {
logrus.Warning("vxlan: Empty destination")
continue
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"bridge", "fdb",
"append", "00:00:00:00:00:00",
"dev", iface,
"dst", dest,
)
if err != nil {
return
}
}
remDb := ifaceDb.Copy()
remDb.Subtract(newDb)
for destInf := range remDb.Iter() {
dest := destInf.(string)
if dest == "" {
logrus.Warning("vxlan: Empty destination")
continue
}
_, err = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
"No such file",
},
"bridge", "fdb",
"del", "00:00:00:00:00:00",
"dev", iface,
"dst", dest,
)
if err != nil {
return
}
}
}
curDatabase = newCurDb
curDatabaseIfaces = newCurIfaces
return
}
func syncIfaces(stat *state.State, internaIfaces []string,
ifacesData map[string]*ip.Iface, retry bool) (err error) {
cIfaces := curIfaces
nodeSelf := stat.Node()
clearCache := false
lostIfaces := set.NewSet()
for ifaceInf := range cIfaces.Iter() {
iface := ifaceInf.(string)
if ifacesData[iface] == nil {
logrus.WithFields(logrus.Fields{
"iface": iface,
}).Error("vxlan: Lost vxlan interface")
lostIfaces.Add(iface)
}
}
cIfaces.Subtract(lostIfaces)
parentVxIfaces := map[string]string{}
parentBrIfaces := map[string]string{}
vxBrIfaces := map[string]string{}
newIfaces := set.NewSet()
if internaIfaces != nil && stat.VxLan() {
for _, iface := range internaIfaces {
vxIface := vm.GetHostVxlanIface(iface)
brIface := vm.GetHostBridgeIface(iface)
parentVxIfaces[vxIface] = iface
parentBrIfaces[brIface] = iface
vxBrIfaces[vxIface] = brIface
newIfaces.Add(vxIface)
newIfaces.Add(brIface)
}
}
remIfaces := cIfaces.Copy()
remIfaces.Subtract(newIfaces)
for ifaceInf := range remIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "b") {
logrus.WithFields(logrus.Fields{
"bridge": iface,
}).Info("vxlan: Removing bridge")
_, _ = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
},
"ip", "link",
"set", "dev",
iface, "down",
)
_ = iproute.BridgeDelete("", iface)
clearCache = true
}
}
for ifaceInf := range remIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "k") {
logrus.WithFields(logrus.Fields{
"vxlan": iface,
}).Info("vxlan: Removing vxlan")
_, _ = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
},
"ip", "link",
"del", iface,
)
clearCache = true
}
}
addIfaces := newIfaces.Copy()
addIfaces.Subtract(cIfaces)
for ifaceInf := range addIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "k") {
vxId := settings.Hypervisor.VxlanId
destPort := settings.Hypervisor.VxlanDestPort
parentIface := parentVxIfaces[iface]
localIp := ""
if nodeSelf.PrivateIps != nil {
localIp = nodeSelf.PrivateIps[parentIface]
}
if localIp == "" {
if !retry {
nodeSelf.SyncNetwork(true)
err = syncIfaces(stat, internaIfaces, ifacesData, true)
return
}
err = &errortypes.NotFoundError{
errors.New("vxlan: Missing private IP for " +
"internal interface"),
}
return
}
logrus.WithFields(logrus.Fields{
"vxlan": iface,
}).Info("vxlan: Adding vxlan")
_, err = utils.ExecCombinedOutputLogged(
[]string{
"File exists",
},
"ip", "link",
"add", iface,
"type", "vxlan",
"id", strconv.Itoa(vxId),
"local", localIp,
"dstport", strconv.Itoa(destPort),
"dev", parentIface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev",
iface, "up",
)
if err != nil {
return
}
clearCache = true
}
}
for ifaceInf := range addIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "b") {
parentIface := parentBrIfaces[iface]
logrus.WithFields(logrus.Fields{
"bridge": iface,
}).Info("vxlan: Adding bridge")
err = iproute.BridgeAdd("", iface)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link", "set",
vm.GetHostVxlanIface(parentIface), "master", iface,
)
if err != nil {
return
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"ip", "link",
"set", "dev",
iface, "up",
)
if err != nil {
return
}
clearCache = true
}
}
existIfaces := cIfaces.Copy()
existIfaces.Subtract(remIfaces)
for ifaceInf := range existIfaces.Iter() {
iface := ifaceInf.(string)
if strings.HasPrefix(iface, "k") {
brIface := vxBrIfaces[iface]
ifaceData := ifacesData[iface]
if ifaceData != nil && ifaceData.Master != brIface {
logrus.WithFields(logrus.Fields{
"vxlan": iface,
"bridge": brIface,
}).Warn("vxlan: Correct vxlan master")
_, err = utils.ExecCombinedOutputLogged(
[]string{"does not exist"},
"ip", "link", "set",
iface, "master", brIface,
)
if err != nil {
return
}
clearCache = true
}
}
}
if clearCache {
ip.ClearIfacesCache("")
}
curIfaces = newIfaces
return
}
func syncDatabase(stat *state.State, internaIfaces []string) (err error) {
nodeSelf := stat.Node()
cDatabase := curDatabase
cIfaces := curDatabaseIfaces
nodes := stat.Nodes()
if nodes == nil {
nodes = []*node.Node{}
}
nodeDc := stat.NodeDatacenter()
if nodeDc == nil {
return
}
newIfaces := set.NewSet()
for _, iface := range internaIfaces {
newIfaces.Add(vm.GetHostVxlanIface(iface))
}
newDb := set.NewSet()
for _, nde := range nodes {
if nde.Id == nodeSelf.Id || nde.Datacenter != nodeDc.Id ||
nde.PrivateIps == nil || !nodeDc.Vxlan() {
continue
}
for _, privateIp := range nde.PrivateIps {
newDb.Add(privateIp)
}
}
addDb := newDb.Copy()
addDb.Subtract(cDatabase)
for destInf := range addDb.Iter() {
dest := destInf.(string)
if dest == "" {
logrus.Warning("vxlan: Empty destination")
continue
}
for ifaceInf := range newIfaces.Iter() {
iface := ifaceInf.(string)
_, err = utils.ExecCombinedOutputLogged(
nil,
"bridge", "fdb",
"append", "00:00:00:00:00:00",
"dev", iface,
"dst", dest,
)
if err != nil {
return
}
}
}
remDb := cDatabase.Copy()
remDb.Subtract(newDb)
for destInf := range remDb.Iter() {
dest := destInf.(string)
if dest == "" {
logrus.Warning("vxlan: Empty destination")
continue
}
for ifaceInf := range newIfaces.Iter() {
iface := ifaceInf.(string)
_, err = utils.ExecCombinedOutputLogged(
[]string{
"Cannot find device",
"No such file",
},
"bridge", "fdb",
"del", "00:00:00:00:00:00",
"dev", iface,
"dst", dest,
)
if err != nil {
return
}
}
}
addIfaces := newIfaces.Copy()
addIfaces.Subtract(cIfaces)
for ifaceInf := range addIfaces.Iter() {
iface := ifaceInf.(string)
for destInf := range newDb.Iter() {
dest := destInf.(string)
if dest == "" {
logrus.Warning("vxlan: Empty destination")
continue
}
_, err = utils.ExecCombinedOutputLogged(
nil,
"bridge", "fdb",
"append", "00:00:00:00:00:00",
"dev", iface,
"dst", dest,
)
if err != nil {
return
}
}
}
curDatabase = newDb
curDatabaseIfaces = newIfaces
return
}
func ApplyState(stat *state.State) (err error) {
nodeSelf := stat.Node()
internaIfaces := nodeSelf.InternalInterfaces
if curIfaces == nil {
err = initIfaces(stat, internaIfaces)
if err != nil {
return
}
}
if curDatabase == nil {
err = initDatabase(stat, internaIfaces)
if err != nil {
return
}
}
ifacesData, err := ip.GetIfacesCached("")
if err != nil {
return
}
err = syncIfaces(stat, internaIfaces, ifacesData, false)
if err != nil {
return
}
err = syncDatabase(stat, internaIfaces)
if err != nil {
return
}
return
}
================================================
FILE: www/.gitignore
================================================
*.js
*.map
node_modules/*
jspm_packages/*
!webpack.config.js
!webpack.dev.config.js
!static/*
!dist/**/*.js
!dist/**/*.map
================================================
FILE: www/README.md
================================================
### pritunl-cloud-www
```
npm install
cd ./node_modules/@github/webauthn-json/dist/
ln -sf ./esm/* ./
cd ../../../../
```
### development
```
./node_modules/.bin/tsc --watch
./node_modules/.bin/webpack-cli --config webpack.dev.config --progress --color --watch
```
#### production
```
sh build.sh
```
### clean
```
rm -rf app/*.js*
rm -rf app/**/*.js*
```
### internal
```
# desktop
rsync --human-readable --archive --xattrs --progress --delete --exclude "/node_modules/*" --exclude "/jspm_packages/*" --exclude "app/*.js" --exclude "app/*.js.map" --exclude "app/**/*.js" --exclude "app/**/*.js.map" /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/
# npm-server
cd /home/cloud/pritunl-cloud-www/
rm package-lock.json
rm -rf node_modules
npm install
rm ./node_modules/react-stripe-checkout/index.d.ts
cd ./node_modules/@github/webauthn-json/dist/
ln -sf ./esm/* ./
cd ../../../../
# desktop
scp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package.json /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/package.json
scp $NPM_SERVER:/home/cloud/pritunl-cloud-www/package-lock.json /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/package-lock.json
rsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/node_modules/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/node_modules/
rsync --human-readable --archive --xattrs --progress --delete --exclude "/node_modules/*" --exclude "/jspm_packages/*" --exclude "app/*.js" --exclude "app/*.js.map" --exclude "app/**/*.js" --exclude "app/**/*.js.map" /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/ $NPM_SERVER:/home/cloud/pritunl-cloud-www/
# npm-server
sh build.sh
# desktop
rsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/dist/
rsync --human-readable --archive --xattrs --progress --delete $NPM_SERVER:/home/cloud/pritunl-cloud-www/dist-dev/ /home/cloud/go/src/github.com/pritunl/pritunl-cloud/www/dist-dev/
```
================================================
FILE: www/app/Alert.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Blueprint from '@blueprintjs/core';
let toaster: Blueprint.Toaster;
export function success(message: string, timeout?: number): string {
if (timeout === undefined) {
timeout = 5000;
}
return toaster.show({
intent: Blueprint.Intent.SUCCESS,
message: message,
timeout: timeout,
});
}
export function info(message: string, timeout?: number): string {
if (timeout === undefined) {
timeout = 5000;
}
return toaster.show({
intent: Blueprint.Intent.PRIMARY,
message: message,
timeout: timeout,
});
}
export function warning(message: string, timeout?: number): string {
if (timeout === undefined) {
timeout = 5000;
}
return toaster.show({
intent: Blueprint.Intent.WARNING,
message: message,
timeout: timeout,
});
}
export function error(message: string, timeout?: number): string {
if (timeout === undefined) {
timeout = 5000;
}
return toaster.show({
intent: Blueprint.Intent.DANGER,
message: message,
timeout: timeout,
});
}
export function errorRes(res: SuperAgent.Response, message: string,
timeout?: number): string {
if (timeout === undefined) {
timeout = 5000;
}
try {
message = res.body.error_msg || message;
} catch(err) {
}
return toaster.show({
intent: Blueprint.Intent.DANGER,
message: message,
timeout: timeout,
});
}
export function dismiss(key: string) {
toaster.dismiss(key);
}
export function init() {
if (toaster) {
return;
}
if (Blueprint.OverlayToaster) {
Blueprint.OverlayToaster.createAsync({
position: Blueprint.Position.BOTTOM,
}).then((toastr) => {
toaster = toastr
});
} else {
console.error('Failed to load toaster')
}
}
================================================
FILE: www/app/App.tsx
================================================
///
import * as Monaco from "monaco-editor";
import * as MonacoEditor from "@monaco-editor/react";
MonacoEditor.loader.config({
monaco: Monaco
})
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as Blueprint from '@blueprintjs/core';
import Main from './components/Main';
import * as Alert from './Alert';
import * as Event from './Event';
import * as Csrf from './Csrf';
import * as MiscUtils from './utils/MiscUtils';
import * as CompletionActions from './actions/CompletionActions';
import hljs from 'highlight.js/lib/core';
import plaintext from 'highlight.js/lib/languages/plaintext';
import bash from 'highlight.js/lib/languages/bash';
import python from 'highlight.js/lib/languages/python';
import yaml from 'highlight.js/lib/languages/yaml';
Csrf.load().then((): void => {
Blueprint.FocusStyleManager.onlyShowFocusOnTabs();
Alert.init();
Event.init();
new MiscUtils.SyncInterval(
async () => {
let lastSync = CompletionActions.lastSync()
if (lastSync && (Date.now() - lastSync) > 5000) {
CompletionActions.sync();
}
},
1000,
)
hljs.registerLanguage('plaintext', plaintext)
hljs.registerLanguage('shell', bash)
hljs.registerLanguage('python', python)
hljs.registerLanguage('yaml', yaml)
ReactDOM.render(
,
document.getElementById('app'),
);
});
================================================
FILE: www/app/Constants.ts
================================================
///
import * as MobileDetect from 'mobile-detect';
let md = new MobileDetect(window.navigator.userAgent);
export const user: boolean = !!(window as any).user;
export const mobile = !!md.mobile();
export const mobileOs = md.os();
export const loadDelay = 700;
export const u2fErrorCodes: {[index: number]: string} = {
0: 'ok',
1: 'other',
2: 'bad request',
3: 'configuration unsupported',
4: 'device ineligible',
5: 'timed out',
};
export const sessionTypes: {[key: string]: string} = {
admin: 'Admin',
user: 'User',
};
export const operatingSystems: {[key: string]: string} = {
linux: 'Linux',
macos_1010: 'macOS 10.10',
macos_1011: 'macOS 10.11',
macos_1012: 'macOS 10.12',
macos_1013: 'macOS 10.13',
macos_1014: 'macOS 10.14',
macos_1015: 'macOS 10.15',
macos11: 'macOS 11',
macos12: 'macOS 12',
macos13: 'macOS 13',
macos14: 'macOS 14',
macos15: 'macOS 15',
macos16: 'macOS 16',
windows_xp: 'Windows XP',
windows_7: 'Windows 7',
windows_vista: 'Windows Vista',
windows_8: 'Windows 8',
windows_10: 'Windows 10',
windows_11: 'Windows 11',
chrome_os: 'Chrome OS',
ios_8: 'iOS 8',
ios_9: 'iOS 9',
ios_10: 'iOS 10',
ios_11: 'iOS 11',
ios_12: 'iOS 12',
ios_13: 'iOS 13',
ios_14: 'iOS 14',
ios_15: 'iOS 15',
ios_16: 'iOS 16',
ios_17: 'iOS 17',
ios_18: 'iOS 18',
ios_19: 'iOS 19',
ios_20: 'iOS 20',
android_4: 'Android KitKat 4.4',
android_5: 'Android Lollipop 5',
android_6: 'Android Marshmallow 6',
android_7: 'Android Nougat 7',
android_8: 'Android Oreo 8',
android_9: 'Android Pie 9',
android_10: 'Android 10',
android_11: 'Android 11',
android_12: 'Android 12',
android_13: 'Android 13',
android_14: 'Android 14',
android_15: 'Android 15',
android_16: 'Android 16',
blackberry_10: 'Blackerry 10',
windows_phone: 'Windows Phone',
firefox_os: 'Firefox OS',
kindle: 'Kindle',
};
export const browsers: {[key: string]: string} = {
chrome: 'Chrome',
chrome_mobile: 'Chrome Mobile',
safari: 'Safari',
safari_mobile: 'Safari Mobile',
firefox: 'Firefox',
firefox_mobile: 'Firefox Mobile',
edge: 'Microsoft Edge',
internet_explorer: 'Internet Explorer',
internet_explorer_mobile: 'Internet Explorer Mobile',
opera: 'Opera',
opera_mobile: 'Opera Mobile',
};
export const locations: {[key: string]: string} = {
US: 'United States',
US_AL: 'Alabama, US',
US_AK: 'Alaska, US',
US_AZ: 'Arizona, US',
US_AR: 'Arkansas, US',
US_CA: 'California, US',
US_CO: 'Colorado, US',
US_CT: 'Connecticut, US',
US_DE: 'Delaware, US',
US_FL: 'Florida, US',
US_GA: 'Georgia, US',
US_HI: 'Hawaii, US',
US_ID: 'Idaho, US',
US_IL: 'Illinois, US',
US_IN: 'Indiana, US',
US_IA: 'Iowa, US',
US_KS: 'Kansas, US',
US_KY: 'Kentucky, US',
US_LA: 'Louisiana, US',
US_ME: 'Maine, US',
US_MD: 'Maryland, US',
US_MA: 'Massachusetts, US',
US_MI: 'Michigan, US',
US_MN: 'Minnesota, US',
US_MS: 'Mississippi, US',
US_MO: 'Missouri, US',
US_MT: 'Montana, US',
US_NE: 'Nebraska, US',
US_NV: 'Nevada, US',
US_NH: 'New Hampshire, US',
US_NJ: 'New Jersey, US',
US_NM: 'New Mexico, US',
US_NY: 'New York, US',
US_NC: 'North Carolina, US',
US_ND: 'North Dakota, US',
US_OH: 'Ohio, US',
US_OK: 'Oklahoma, US',
US_OR: 'Oregon, US',
US_PA: 'Pennsylvania, US',
US_RI: 'Rhode Island, US',
US_SC: 'South Carolina, US',
US_SD: 'South Dakota, US',
US_TN: 'Tennessee, US',
US_TX: 'Texas, US',
US_UT: 'Utah, US',
US_VT: 'Vermont, US',
US_VA: 'Virginia, US',
US_WA: 'Washington, US',
US_DC: 'Washington DC, US',
US_WV: 'West Virginia, US',
US_WI: 'Wisconsin, US',
US_WY: 'Wyoming, US',
AF: 'Afghanistan',
AX: 'Åland Islands',
AL: 'Albania',
DZ: 'Algeria',
AS: 'American Samoa',
AD: 'Andorra',
AO: 'Angola',
AI: 'Anguilla',
AQ: 'Antarctica',
AG: 'Antigua and Barbuda',
AR: 'Argentina',
AM: 'Armenia',
AW: 'Aruba',
AU: 'Australia',
AT: 'Austria',
AZ: 'Azerbaijan',
BS: 'Bahamas',
BH: 'Bahrain',
BD: 'Bangladesh',
BB: 'Barbados',
BY: 'Belarus',
BE: 'Belgium',
BZ: 'Belize',
BJ: 'Benin',
BM: 'Bermuda',
BT: 'Bhutan',
BO: 'Bolivia',
BQ: 'Bonaire',
BA: 'Bosnia and Herzegovina',
BW: 'Botswana',
BV: 'Bouvet Island',
BR: 'Brazil',
IO: 'British Indian Ocean Territory',
BN: 'Brunei Darussalam',
BG: 'Bulgaria',
BF: 'Burkina Faso',
BI: 'Burundi',
CV: 'Cabo Verde',
KH: 'Cambodia',
CM: 'Cameroon',
CA: 'Canada',
KY: 'Cayman Islands',
CF: 'Central African Republic',
TD: 'Chad',
CL: 'Chile',
CN: 'China',
CX: 'Christmas Island',
CC: 'Cocos Islands',
CO: 'Colombia',
KM: 'Comoros',
CG: 'Congo',
CD: 'Congo Democratic Republic',
CK: 'Cook Islands',
CR: 'Costa Rica',
CI: 'Côte dIvoire',
HR: 'Croatia',
CU: 'Cuba',
CW: 'Curaçao',
CY: 'Cyprus',
CZ: 'Czechia',
DK: 'Denmark',
DJ: 'Djibouti',
DM: 'Dominica',
DO: 'Dominican Republic',
EC: 'Ecuador',
EG: 'Egypt',
SV: 'El Salvador',
GQ: 'Equatorial Guinea',
ER: 'Eritrea',
EE: 'Estonia',
ET: 'Ethiopia',
FK: 'Falkland Islands',
FO: 'Faroe Islands',
FJ: 'Fiji',
FI: 'Finland',
FR: 'France',
GF: 'French Guiana',
PF: 'French Polynesia',
TF: 'French Southern Territories',
GA: 'Gabon',
GM: 'Gambia',
GE: 'Georgia',
DE: 'Germany',
GH: 'Ghana',
GI: 'Gibraltar',
GR: 'Greece',
GL: 'Greenland',
GD: 'Grenada',
GP: 'Guadeloupe',
GU: 'Guam',
GT: 'Guatemala',
GG: 'Guernsey',
GN: 'Guinea',
GW: 'Guinea-Bissau',
GY: 'Guyana',
HT: 'Haiti',
HM: 'Heard Island and McDonald Islands',
VA: 'Holy See',
HN: 'Honduras',
HK: 'Hong Kong',
HU: 'Hungary',
IS: 'Iceland',
IN: 'India',
ID: 'Indonesia',
IR: 'Iran',
IQ: 'Iraq',
IE: 'Ireland',
IM: 'Isle of Man',
IL: 'Israel',
IT: 'Italy',
JM: 'Jamaica',
JP: 'Japan',
JE: 'Jersey',
JO: 'Jordan',
KZ: 'Kazakhstan',
KE: 'Kenya',
KI: 'Kiribati',
KP: 'North Korea',
KR: 'South Korea',
KW: 'Kuwait',
KG: 'Kyrgyzstan',
LA: 'Lao Peoples',
LV: 'Latvia',
LB: 'Lebanon',
LS: 'Lesotho',
LR: 'Liberia',
LY: 'Libya',
LI: 'Liechtenstein',
LT: 'Lithuania',
LU: 'Luxembourg',
MO: 'Macao',
MK: 'Macedonia',
MG: 'Madagascar',
MW: 'Malawi',
MY: 'Malaysia',
MV: 'Maldives',
ML: 'Mali',
MT: 'Malta',
MH: 'Marshall Islands',
MQ: 'Martinique',
MR: 'Mauritania',
MU: 'Mauritius',
YT: 'Mayotte',
MX: 'Mexico',
FM: 'Micronesia',
MD: 'Moldova',
MC: 'Monaco',
MN: 'Mongolia',
ME: 'Montenegro',
MS: 'Montserrat',
MA: 'Morocco',
MZ: 'Mozambique',
MM: 'Myanmar',
NA: 'Namibia',
NR: 'Nauru',
NP: 'Nepal',
NL: 'Netherlands',
NC: 'New Caledonia',
NZ: 'New Zealand',
NI: 'Nicaragua',
NE: 'Niger',
NG: 'Nigeria',
NU: 'Niue',
NF: 'Norfolk Island',
MP: 'Northern Mariana Islands',
NO: 'Norway',
OM: 'Oman',
PK: 'Pakistan',
PW: 'Palau',
PS: 'Palestine, State of',
PA: 'Panama',
PG: 'Papua New Guinea',
PY: 'Paraguay',
PE: 'Peru',
PH: 'Philippines',
PN: 'Pitcairn',
PL: 'Poland',
PT: 'Portugal',
PR: 'Puerto Rico',
QA: 'Qatar',
RE: 'Réunion',
RO: 'Romania',
RU: 'Russian Federation',
RW: 'Rwanda',
BL: 'Saint Barthélemy',
SH: 'Saint Helena',
KN: 'Saint Kitts and Nevis',
LC: 'Saint Lucia',
MF: 'Saint Martin',
PM: 'Saint Pierre and Miquelon',
VC: 'Saint Vincent and the Grenadines',
WS: 'Samoa',
SM: 'San Marino',
ST: 'Sao Tome and Principe',
SA: 'Saudi Arabia',
SN: 'Senegal',
RS: 'Serbia',
SC: 'Seychelles',
SL: 'Sierra Leone',
SG: 'Singapore',
SX: 'Sint Maarten',
SK: 'Slovakia',
SI: 'Slovenia',
SB: 'Solomon Islands',
SO: 'Somalia',
ZA: 'South Africa',
GS: 'South Georgia and the South Sandwich Islands',
SS: 'South Sudan',
ES: 'Spain',
LK: 'Sri Lanka',
SD: 'Sudan',
SR: 'Suriname',
SJ: 'Svalbard and Jan Mayen',
SZ: 'Swaziland',
SE: 'Sweden',
CH: 'Switzerland',
SY: 'Syrian Arab Republic',
TW: 'Taiwan',
TJ: 'Tajikistan',
TZ: 'Tanzania',
TH: 'Thailand',
TL: 'Timor-Leste',
TG: 'Togo',
TK: 'Tokelau',
TO: 'Tonga',
TT: 'Trinidad and Tobago',
TN: 'Tunisia',
TR: 'Turkey',
TM: 'Turkmenistan',
TC: 'Turks and Caicos Islands',
TV: 'Tuvalu',
UG: 'Uganda',
UA: 'Ukraine',
AE: 'United Arab Emirates',
GB: 'United Kingdom',
UM: 'United States Minor Outlying Islands',
UY: 'Uruguay',
UZ: 'Uzbekistan',
VU: 'Vanuatu',
VE: 'Venezuela',
VN: 'Viet Nam',
VG: 'British Virgin Islands',
VI: 'US Virgin Islands',
WF: 'Wallis and Futuna',
EH: 'Western Sahara',
YE: 'Yemen',
ZM: 'Zambia',
ZW: 'Zimbabwe',
};
================================================
FILE: www/app/Csrf.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as License from './License';
import * as Theme from './Theme';
export let token = '';
export function load(): Promise {
return new Promise((resolve, reject): void => {
SuperAgent
.get('/csrf')
.set('Accept', 'application/json')
.end((err: any, res: SuperAgent.Response): void => {
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
reject(err);
return;
}
token = res.body.token;
License.setOracle(!!res.body.oracle_license);
let theme = res.body.theme
if (theme) {
let themeParts = theme.split("-")
if (themeParts[1] === "3") {
Theme.themeVer3()
} else {
Theme.themeVer5()
}
if (themeParts[0] === "light") {
Theme.light();
} else {
Theme.dark();
}
} else {
Theme.dark();
}
if (res.body.editor_theme) {
Theme.setEditorTheme(res.body.editor_theme);
}
resolve();
});
});
}
================================================
FILE: www/app/EditorThemes.ts
================================================
// The MIT License (MIT)
// Copyright (c) Brijesh Bittu
// 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.
import * as Monaco from "monaco-editor"
let allHallowsEve = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "000000","token": ""},
{"foreground": "ffffff","background": "434242","token": "text"},
{"foreground": "ffffff","background": "000000","token": "source"},
{"foreground": "9933cc","token": "comment"},
{"foreground": "3387cc","token": "constant"},
{"foreground": "cc7833","token": "keyword"},
{"foreground": "d0d0ff","token": "meta.preprocessor.c"},
{"fontStyle": "italic","token": "variable.parameter"},
{"foreground": "ffffff","background": "9b9b9b","token": "source comment.block"},
{"foreground": "66cc33","token": "string"},
{"foreground": "aaaaaa","token": "string constant.character.escape"},
{"foreground": "000000","background": "cccc33","token": "string.interpolated"},
{"foreground": "cccc33","token": "string.regexp"},
{"foreground": "cccc33","token": "string.literal"},
{"foreground": "555555","token": "string.interpolated constant.character.escape"},
{"fontStyle": "underline","token": "entity.name.type"},
{"fontStyle": "italic underline","token": "entity.other.inherited-class"},
{"fontStyle": "underline","token": "entity.name.tag"},
{"foreground": "c83730","token": "support.function"}
],
"colors": {
"editor.foreground": "#FFFFFF",
"editor.background": "#000000",
"editor.selectionBackground": "#73597EE0",
"editor.lineHighlightBackground": "#333300",
"editorCursor.foreground": "#FFFFFF",
"editorWhitespace.foreground": "#404040",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let amy = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "200020","token": ""},
{"foreground": "404080","background": "200020","fontStyle": "italic","token": "comment.block"},
{"foreground": "999999","token": "string"},
{"foreground": "707090","token": "constant.language"},
{"foreground": "7090b0","token": "constant.numeric"},
{"fontStyle": "bold","token": "constant.numeric.integer.int32"},
{"fontStyle": "italic","token": "constant.numeric.integer.int64"},
{"fontStyle": "bold italic","token": "constant.numeric.integer.nativeint"},
{"fontStyle": "underline","token": "constant.numeric.floating-point.ocaml"},
{"foreground": "666666","token": "constant.character"},
{"foreground": "8080a0","token": "constant.language.boolean"},
{"foreground": "008080","token": "variable.language"},
{"foreground": "008080","token": "variable.other"},
{"foreground": "a080ff","token": "keyword"},
{"foreground": "a0a0ff","token": "keyword.operator"},
{"foreground": "d0d0ff","token": "keyword.other.decorator"},
{"fontStyle": "underline","token": "keyword.operator.infix.floating-point.ocaml"},
{"fontStyle": "underline","token": "keyword.operator.prefix.floating-point.ocaml"},
{"foreground": "c080c0","token": "keyword.other.directive"},
{"foreground": "c080c0","fontStyle": "underline","token": "keyword.other.directive.line-number"},
{"foreground": "80a0ff","token": "keyword.control"},
{"foreground": "b0fff0","token": "storage"},
{"foreground": "60b0ff","token": "entity.name.type.variant"},
{"foreground": "60b0ff","fontStyle": "italic","token": "storage.type.variant.polymorphic"},
{"foreground": "60b0ff","fontStyle": "italic","token": "entity.name.type.variant.polymorphic"},
{"foreground": "b000b0","token": "entity.name.type.module"},
{"foreground": "b000b0","fontStyle": "underline","token": "entity.name.type.module-type.ocaml"},
{"foreground": "a00050","token": "support.other"},
{"foreground": "70e080","token": "entity.name.type.class"},
{"foreground": "70e0a0","token": "entity.name.type.class-type"},
{"foreground": "50a0a0","token": "entity.name.function"},
{"foreground": "80b0b0","token": "variable.parameter"},
{"foreground": "3080a0","token": "entity.name.type.token"},
{"foreground": "3cb0d0","token": "entity.name.type.token.reference"},
{"foreground": "90e0e0","token": "entity.name.function.non-terminal"},
{"foreground": "c0f0f0","token": "entity.name.function.non-terminal.reference"},
{"foreground": "009090","token": "entity.name.tag"},
{"background": "200020","token": "support.constant"},
{"foreground": "400080","background": "ffff00","fontStyle": "bold","token": "invalid.illegal"},
{"foreground": "200020","background": "cc66ff","token": "invalid.deprecated"},
{"background": "40008054","token": "source.camlp4.embedded"},
{"foreground": "805080","token": "punctuation"}
],
"colors": {
"editor.foreground": "#D0D0FF",
"editor.background": "#200020",
"editor.selectionBackground": "#80000080",
"editor.lineHighlightBackground": "#80000040",
"editorCursor.foreground": "#7070FF",
"editorWhitespace.foreground": "#BFBFBF",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let birdsOfParadise = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "372725","token": ""},
{"foreground": "e6e1c4","background": "322323","token": "source"},
{"foreground": "6b4e32","fontStyle": "italic","token": "comment"},
{"foreground": "ef5d32","token": "keyword"},
{"foreground": "ef5d32","token": "storage"},
{"foreground": "efac32","token": "entity.name.function"},
{"foreground": "efac32","token": "keyword.other.name-of-parameter.objc"},
{"foreground": "efac32","fontStyle": "bold","token": "entity.name"},
{"foreground": "6c99bb","token": "constant.numeric"},
{"foreground": "7daf9c","token": "variable.language"},
{"foreground": "7daf9c","token": "variable.other"},
{"foreground": "6c99bb","token": "constant"},
{"foreground": "efac32","token": "variable.other.constant"},
{"foreground": "6c99bb","token": "constant.language"},
{"foreground": "d9d762","token": "string"},
{"foreground": "efac32","token": "support.function"},
{"foreground": "efac32","token": "support.type"},
{"foreground": "6c99bb","token": "support.constant"},
{"foreground": "efcb43","token": "meta.tag"},
{"foreground": "efcb43","token": "declaration.tag"},
{"foreground": "efcb43","token": "entity.name.tag"},
{"foreground": "efcb43","token": "entity.other.attribute-name"},
{"foreground": "ffffff","background": "990000","token": "invalid"},
{"foreground": "7daf9c","token": "constant.character.escaped"},
{"foreground": "7daf9c","token": "constant.character.escape"},
{"foreground": "7daf9c","token": "string source"},
{"foreground": "7daf9c","token": "string source.ruby"},
{"foreground": "e6e1dc","background": "144212","token": "markup.inserted"},
{"foreground": "e6e1dc","background": "660000","token": "markup.deleted"},
{"background": "2f33ab","token": "meta.diff.header"},
{"background": "2f33ab","token": "meta.separator.diff"},
{"background": "2f33ab","token": "meta.diff.index"},
{"background": "2f33ab","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#E6E1C4",
"editor.background": "#372725",
"editor.selectionBackground": "#16120E",
"editor.lineHighlightBackground": "#1F1611",
"editorCursor.foreground": "#E6E1C4",
"editorWhitespace.foreground": "#42302D",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let blackboard = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "0C1021","token": ""},
{"foreground": "aeaeae","token": "comment"},
{"foreground": "d8fa3c","token": "constant"},
{"foreground": "ff6400","token": "entity"},
{"foreground": "fbde2d","token": "keyword"},
{"foreground": "fbde2d","token": "storage"},
{"foreground": "61ce3c","token": "string"},
{"foreground": "61ce3c","token": "meta.verbatim"},
{"foreground": "8da6ce","token": "support"},
{"foreground": "ab2a1d","fontStyle": "italic","token": "invalid.deprecated"},
{"foreground": "f8f8f8","background": "9d1e15","token": "invalid.illegal"},
{"foreground": "ff6400","fontStyle": "italic","token": "entity.other.inherited-class"},
{"foreground": "ff6400","token": "string constant.other.placeholder"},
{"foreground": "becde6","token": "meta.function-call.py"},
{"foreground": "7f90aa","token": "meta.tag"},
{"foreground": "7f90aa","token": "meta.tag entity"},
{"foreground": "ffffff","token": "entity.name.section"},
{"foreground": "d5e0f3","token": "keyword.type.variant"},
{"foreground": "f8f8f8","token": "source.ocaml keyword.operator.symbol"},
{"foreground": "8da6ce","token": "source.ocaml keyword.operator.symbol.infix"},
{"foreground": "8da6ce","token": "source.ocaml keyword.operator.symbol.prefix"},
{"fontStyle": "underline","token": "source.ocaml keyword.operator.symbol.infix.floating-point"},
{"fontStyle": "underline","token": "source.ocaml keyword.operator.symbol.prefix.floating-point"},
{"fontStyle": "underline","token": "source.ocaml constant.numeric.floating-point"},
{"background": "ffffff08","token": "text.tex.latex meta.function.environment"},
{"background": "7a96fa08","token": "text.tex.latex meta.function.environment meta.function.environment"},
{"foreground": "fbde2d","token": "text.tex.latex support.function"},
{"foreground": "ffffff","token": "source.plist string.unquoted"},
{"foreground": "ffffff","token": "source.plist keyword.operator"}
],
"colors": {
"editor.foreground": "#F8F8F8",
"editor.background": "#0C1021",
"editor.selectionBackground": "#253B76",
"editor.lineHighlightBackground": "#FFFFFF0F",
"editorCursor.foreground": "#FFFFFFA6",
"editorWhitespace.foreground": "#FFFFFF40",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let brillianceBlack = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "0D0D0DFA","token": ""},
{"foreground": "000000","background": "ffffff","fontStyle": "bold","token": "meta.thomas_aylott"},
{"foreground": "555555","background": "ffffff","fontStyle": "underline","token": "meta.subtlegradient"},
{"foreground": "fffc80","background": "803d0033","token": "string -meta.tag -meta.doctype -string.regexp -string.literal -string.interpolated -string.quoted.literal -string.unquoted"},
{"foreground": "fffc80","background": "803d0033","token": "variable.parameter.misc.css"},
{"foreground": "fffc80","background": "803d0033","token": "text string source string"},
{"foreground": "fffc80","background": "803d0033","token": "string.unquoted string"},
{"foreground": "fffc80","background": "803d0033","token": "string.regexp string"},
{"foreground": "fffc80","background": "803d0033","token": "string.interpolated string"},
{"foreground": "fffc80","background": "803d0033","token": "meta.tag source string"},
{"foreground": "803d00","token": "punctuation.definition.string -meta.tag"},
{"foreground": "fff80033","token": "string.regexp punctuation.definition.string"},
{"foreground": "fff80033","token": "string.quoted.literal punctuation.definition.string"},
{"foreground": "fff80033","token": "string.quoted.double.ruby.mod punctuation.definition.string"},
{"foreground": "fff800","background": "43800033","token": "string.quoted.literal"},
{"foreground": "fff800","background": "43800033","token": "string.quoted.double.ruby.mod"},
{"foreground": "ffbc80","token": "string.unquoted -string.unquoted.embedded"},
{"foreground": "ffbc80","token": "string.quoted.double.multiline"},
{"foreground": "ffbc80","token": "meta.scope.heredoc"},
{"foreground": "fffc80","background": "1a1a1a","token": "string.interpolated"},
{"foreground": "fff800","background": "43800033","token": "string.regexp"},
{"background": "43800033","token": "string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group string.regexp.group string.regexp.group"},
{"foreground": "86ff00","background": "43800033","token": "string.regexp.character-class"},
{"foreground": "00fff8","background": "43800033","token": "string.regexp.arbitrary-repitition"},
{"foreground": "803d00","token": "string.regexp punctuation.definition.string keyword.other"},
{"background": "0086ff33","token": "meta.group.assertion.regexp"},
{"foreground": "0086ff","token": "meta.assertion"},
{"foreground": "0086ff","token": "meta.group.assertion keyword.control.group.regexp"},
{"foreground": "0086ff","token": "meta.group.assertion punctuation.definition.group"},
{"foreground": "c6ff00","token": "constant.numeric"},
{"foreground": "86ff00","token": "constant.character"},
{"foreground": "07ff00","token": "constant.language"},
{"foreground": "07ff00","token": "keyword.other.unit"},
{"foreground": "07ff00","token": "constant.other.java"},
{"foreground": "07ff00","token": "constant.other.unit"},
{"foreground": "07ff00","background": "04800033","token": "constant.language.pseudo-variable"},
{"foreground": "00ff79","token": "constant.other"},
{"foreground": "00ff79","token": "constant.block"},
{"foreground": "00fff8","token": "support.constant"},
{"foreground": "00fff8","token": "constant.name"},
{"foreground": "00ff79","background": "00807c33","token": "variable.other.readwrite.global.pre-defined"},
{"foreground": "00ff79","background": "00807c33","token": "variable.language"},
{"foreground": "00fff8","token": "variable.other.constant"},
{"foreground": "00fff8","background": "00807c33","token": "support.variable"},
{"foreground": "00807c","background": "00438033","token": "variable.other.readwrite.global"},
{"foreground": "31a6ff","token": "variable.other"},
{"foreground": "31a6ff","token": "variable.js"},
{"foreground": "31a6ff","token": "punctuation.separator.variable"},
{"foreground": "0086ff","background": "0008ff33","token": "variable.other.readwrite.class"},
{"foreground": "406180","token": "variable.other.readwrite.instance"},
{"foreground": "406180","token": "variable.other.php"},
{"foreground": "406180","token": "variable.other.normal"},
{"foreground": "00000080","token": "punctuation.definition"},
{"foreground": "00000080","token": "punctuation.separator.variable"},
{"foreground": "7e0080","token": "storage -storage.modifier"},
{"background": "803d0033","token": "other.preprocessor"},
{"background": "803d0033","token": "entity.name.preprocessor"},
{"foreground": "666666","token": "variable.language.this.js"},
{"foreground": "803d00","token": "storage.modifier"},
{"foreground": "ff0000","token": "entity.name.class"},
{"foreground": "ff0000","token": "entity.name.type.class"},
{"foreground": "ff0000","token": "entity.name.type.module"},
{"foreground": "870000","background": "ff000033","token": "meta.class -meta.class.instance"},
{"foreground": "870000","background": "ff000033","token": "declaration.class"},
{"foreground": "870000","background": "ff000033","token": "meta.definition.class"},
{"foreground": "870000","background": "ff000033","token": "declaration.module"},
{"foreground": "ff0000","background": "87000033","token": "support.type"},
{"foreground": "ff0000","background": "87000033","token": "support.class"},
{"foreground": "ff3d44","token": "entity.name.instance"},
{"foreground": "ff3d44","token": "entity.name.type.instance"},
{"background": "831e5133","token": "meta.class.instance.constructor"},
{"foreground": "ff0086","background": "80000433","token": "entity.other.inherited-class"},
{"foreground": "ff0086","background": "80000433","token": "entity.name.module"},
{"foreground": "ff0086","token": "meta.definition.method"},
{"foreground": "ff0086","token": "entity.name.function"},
{"foreground": "ff0086","token": "entity.name.preprocessor"},
{"foreground": "9799ff","token": "variable.parameter.function"},
{"foreground": "9799ff","token": "variable.parameter -variable.parameter.misc.css"},
{"foreground": "9799ff","token": "meta.definition.method meta.definition.param-list"},
{"foreground": "9799ff","token": "meta.function.method.with-arguments variable.parameter.function"},
{"foreground": "800004","token": "punctuation.definition.parameters"},
{"foreground": "800004","token": "variable.parameter.function punctuation.separator.object"},
{"foreground": "782ec1","token": "keyword.other.special-method"},
{"foreground": "782ec1","token": "meta.function-call entity.name.function -(meta.function-call meta.function)"},
{"foreground": "782ec1","token": "support.function - variable"},
{"foreground": "9d3eff","token": "meta.function-call support.function - variable"},
{"foreground": "603f80","background": "603f8033","token": "support.function"},
{"foreground": "bc80ff","token": "punctuation.section.function"},
{"foreground": "bc80ff","token": "meta.brace.curly.function"},
{"foreground": "bc80ff","token": "meta.function-call punctuation.section.scope.ruby"},
{"foreground": "bc80ff","token": "meta.function-call punctuation.separator.object"},
{"foreground": "bc80ff","fontStyle": "bold","token": "meta.group.braces.round punctuation.section.scope"},
{"foreground": "bc80ff","fontStyle": "bold","token": "meta.group.braces.round meta.delimiter.object.comma"},
{"foreground": "bc80ff","fontStyle": "bold","token": "meta.group.braces.curly.function meta.delimiter.object.comma"},
{"foreground": "bc80ff","fontStyle": "bold","token": "meta.brace.round"},
{"foreground": "a88fc0","token": "meta.function-call.method.without-arguments"},
{"foreground": "a88fc0","token": "meta.function-call.method.without-arguments entity.name.function"},
{"foreground": "f800ff","token": "keyword.control"},
{"foreground": "7900ff","token": "keyword.other"},
{"foreground": "0000ce","token": "keyword.operator"},
{"foreground": "0000ce","token": "declaration.function.operator"},
{"foreground": "0000ce","token": "meta.preprocessor.c.include"},
{"foreground": "0000ce","token": "punctuation.separator.operator"},
{"foreground": "0000ce","background": "00009a33","token": "keyword.operator.assignment"},
{"foreground": "2136ce","token": "keyword.operator.arithmetic"},
{"foreground": "3759ff","background": "00009a33","token": "keyword.operator.logical"},
{"foreground": "7c88ff","token": "keyword.operator.comparison"},
{"foreground": "800043","token": "meta.class.instance.constructor keyword.operator.new"},
{"foreground": "cccccc","background": "333333","token": "meta.doctype"},
{"foreground": "cccccc","background": "333333","token": "meta.tag.sgml-declaration.doctype"},
{"foreground": "cccccc","background": "333333","token": "meta.tag.sgml.doctype"},
{"foreground": "333333","token": "meta.tag"},
{"foreground": "666666","background": "333333bf","token": "meta.tag.structure"},
{"foreground": "666666","background": "333333bf","token": "meta.tag.segment"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.block"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.xml"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.key"},
{"foreground": "ff7900","background": "803d0033","token": "meta.tag.inline"},
{"background": "803d0033","token": "meta.tag.inline source"},
{"foreground": "ff0007","background": "80000433","token": "meta.tag.other"},
{"foreground": "ff0007","background": "80000433","token": "entity.name.tag.style"},
{"foreground": "ff0007","background": "80000433","token": "entity.name.tag.script"},
{"foreground": "ff0007","background": "80000433","token": "meta.tag.block.script"},
{"foreground": "ff0007","background": "80000433","token": "source.js.embedded punctuation.definition.tag.html"},
{"foreground": "ff0007","background": "80000433","token": "source.css.embedded punctuation.definition.tag.html"},
{"foreground": "0086ff","background": "00438033","token": "meta.tag.form"},
{"foreground": "0086ff","background": "00438033","token": "meta.tag.block.form"},
{"foreground": "f800ff","background": "3c008033","token": "meta.tag.meta"},
{"background": "121212","token": "meta.section.html.head"},
{"background": "0043801a","token": "meta.section.html.form"},
{"foreground": "666666","token": "meta.tag.xml"},
{"foreground": "ffffff4d","token": "entity.name.tag"},
{"foreground": "ffffff33","token": "entity.other.attribute-name"},
{"foreground": "ffffff33","token": "meta.tag punctuation.definition.string"},
{"foreground": "ffffff66","token": "meta.tag string -source -punctuation"},
{"foreground": "ffffff66","token": "text source text meta.tag string -punctuation"},
{"foreground": "999999","token": "text meta.paragraph"},
{"foreground": "fff800","background": "33333333","token": "markup markup -(markup meta.paragraph.list)"},
{"foreground": "000000","background": "ffffff","token": "markup.hr"},
{"foreground": "ffffff","token": "markup.heading"},
{"foreground": "95d4ff80","fontStyle": "bold","token": "markup.bold"},
{"fontStyle": "italic","token": "markup.italic"},
{"fontStyle": "underline","token": "markup.underline"},
{"foreground": "0086ff","token": "meta.reference"},
{"foreground": "0086ff","token": "markup.underline.link"},
{"foreground": "00fff8","background": "00438033","token": "entity.name.reference"},
{"foreground": "00fff8","fontStyle": "underline","token": "meta.reference.list markup.underline.link"},
{"foreground": "00fff8","fontStyle": "underline","token": "text.html.textile markup.underline.link"},
{"background": "80808040","token": "markup.raw.block"},
{"background": "ffffff1a","token": "markup.quote"},
{"foreground": "ffffff","token": "markup.list meta.paragraph"},
{"foreground": "000000","background": "ffffff","token": "text.html.markdown"},
{"foreground": "000000","token": "text.html.markdown meta.paragraph"},
{"foreground": "555555","token": "text.html.markdown markup.list meta.paragraph"},
{"foreground": "000000","fontStyle": "bold","token": "text.html.markdown markup.heading"},
{"foreground": "8a5420","token": "text.html.markdown string"},
{"foreground": "666666","token": "meta.selector"},
{"foreground": "006680","token": "source.css meta.scope.property-list meta.property-value punctuation.definition.arguments"},
{"foreground": "006680","token": "source.css meta.scope.property-list meta.property-value punctuation.separator.arguments"},
{"foreground": "4f00ff","token": "entity.other.attribute-name.pseudo-element"},
{"foreground": "7900ff","token": "entity.other.attribute-name.pseudo-class"},
{"foreground": "7900ff","token": "entity.other.attribute-name.tag.pseudo-class"},
{"foreground": "f800ff","token": "meta.selector entity.other.attribute-name.class"},
{"foreground": "ff0086","token": "meta.selector entity.other.attribute-name.id"},
{"foreground": "ff0007","token": "meta.selector entity.name.tag"},
{"foreground": "ff7900","fontStyle": "bold","token": "entity.name.tag.wildcard"},
{"foreground": "ff7900","fontStyle": "bold","token": "entity.other.attribute-name.universal"},
{"foreground": "c25a00","token": "source.css entity.other.attribute-name.attribute"},
{"foreground": "673000","token": "source.css meta.attribute-selector keyword.operator.comparison"},
{"foreground": "333333","fontStyle": "bold","token": "meta.scope.property-list"},
{"foreground": "999999","token": "meta.property-name"},
{"foreground": "ffffff","background": "0d0d0d","token": "support.type.property-name"},
{"foreground": "999999","background": "19191980","token": "meta.property-value"},
{"background": "000000","token": "text.latex markup.raw"},
{"foreground": "bc80ff","token": "text.latex support.function -support.function.textit -support.function.emph"},
{"foreground": "ffffffbf","token": "text.latex support.function.section"},
{"foreground": "000000","background": "ffffff","token": "text.latex entity.name.section -meta.group -keyword.operator.braces"},
{"background": "00000080","token": "text.latex keyword.operator.delimiter"},
{"foreground": "999999","token": "text.latex keyword.operator.brackets"},
{"foreground": "666666","token": "text.latex keyword.operator.braces"},
{"foreground": "0008ff4d","background": "00008033","token": "meta.footnote"},
{"background": "ffffff0d","token": "text.latex meta.label.reference"},
{"foreground": "ff0007","background": "260001","token": "text.latex keyword.control.ref"},
{"foreground": "ffbc80","background": "400002","token": "text.latex variable.parameter.label.reference"},
{"foreground": "ff0086","background": "260014","token": "text.latex keyword.control.cite"},
{"foreground": "ffbfe1","background": "400022","token": "variable.parameter.cite"},
{"foreground": "ffffff80","token": "text.latex variable.parameter.label"},
{"foreground": "cdcdcd","token": "meta.function markup"},
{"foreground": "33333333","token": "text.latex meta.group.braces"},
{"foreground": "33333333","background": "00000080","token": "text.latex meta.environment.list"},
{"foreground": "33333333","background": "00000080","token": "text.latex meta.environment.list meta.environment.list"},
{"foreground": "33333333","background": "000000","token": "text.latex meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "000000","background": "cccccc","token": "text.latex meta.end-document"},
{"foreground": "000000","background": "cccccc","token": "text.latex meta.begin-document"},
{"foreground": "000000","background": "cccccc","token": "meta.end-document.latex support.function"},
{"foreground": "000000","background": "cccccc","token": "meta.end-document.latex variable.parameter"},
{"foreground": "000000","background": "cccccc","token": "meta.begin-document.latex support.function"},
{"foreground": "000000","background": "cccccc","token": "meta.begin-document.latex variable.parameter"},
{"foreground": "00ffaa","background": "00805533","token": "meta.brace.erb.return-value"},
{"background": "8080801a","token": "source.ruby.rails.embedded.return-value.one-line"},
{"foreground": "00fff8","background": "00fff81a","token": "punctuation.section.embedded -(source string source punctuation.section.embedded)"},
{"foreground": "00fff8","background": "00fff81a","token": "meta.brace.erb.html"},
{"background": "00fff81a","token": "source.ruby.rails.embedded.one-line"},
{"foreground": "406180","token": "source string source punctuation.section.embedded"},
{"background": "0d0d0d","token": "source.js.embedded"},
{"background": "000000","token": "meta.brace.erb"},
{"foreground": "ffffff","background": "33333380","token": "source string source"},
{"foreground": "999999","background": "00000099","token": "source string.interpolated source"},
{"background": "3333331a","token": "source source"},
{"background": "3333331a","token": "source.java.embedded"},
{"foreground": "ffffff","token": "text -text.xml.strict"},
{"foreground": "cccccc","background": "000000","token": "text source"},
{"foreground": "cccccc","background": "000000","token": "meta.scope.django.template"},
{"foreground": "999999","token": "text string source"},
{"foreground": "330004","background": "ff0007","fontStyle": "bold","token": "invalid -invalid.SOMETHING"},
{"foreground": "ff3600","fontStyle": "underline","token": "invalid.SOMETHING"},
{"foreground": "333333","token": "meta.syntax"},
{"foreground": "4c4c4c","background": "33333333","token": "comment -comment.line"},
{"foreground": "4c4c4c","fontStyle": "italic","token": "comment.line"},
{"fontStyle": "italic","token": "text comment.block -source"},
{"foreground": "40ff9a","background": "00401e","token": "markup.inserted"},
{"foreground": "ff40a3","background": "400022","token": "markup.deleted"},
{"foreground": "ffff55","background": "803d00","token": "markup.changed"},
{"foreground": "ffffff","background": "000000","token": "text.subversion-commit meta.scope.changed-files"},
{"foreground": "ffffff","background": "000000","token": "text.subversion-commit meta.scope.changed-files.svn meta.diff.separator"},
{"foreground": "000000","background": "ffffff","token": "text.subversion-commit"},
{"foreground": "7f7f7f","background": "ffffff03","fontStyle": "bold","token": "punctuation.terminator"},
{"foreground": "7f7f7f","background": "ffffff03","fontStyle": "bold","token": "meta.delimiter"},
{"foreground": "7f7f7f","background": "ffffff03","fontStyle": "bold","token": "punctuation.separator.method"},
{"background": "00000080","token": "punctuation.terminator.statement"},
{"background": "00000080","token": "meta.delimiter.statement.js"},
{"background": "00000040","token": "meta.delimiter.object.js"},
{"foreground": "803d00","fontStyle": "bold","token": "string.quoted.single.brace"},
{"foreground": "803d00","fontStyle": "bold","token": "string.quoted.double.brace"},
{"foreground": "333333","background": "dcdcdc","token": "text.blog"},
{"foreground": "333333","background": "dcdcdc","token": "text.mail"},
{"foreground": "cccccc","background": "000000","token": "text.blog text"},
{"foreground": "cccccc","background": "000000","token": "text.mail text"},
{"foreground": "06403e","background": "00fff81a","token": "meta.header.blog keyword.other"},
{"foreground": "06403e","background": "00fff81a","token": "meta.header.mail keyword.other"},
{"foreground": "803d00","background": "ffff551a","token": "meta.header.blog string.unquoted.blog"},
{"foreground": "803d00","background": "ffff551a","token": "meta.header.mail string.unquoted"},
{"foreground": "ff0000","token": "source.ocaml entity.name.type.module"},
{"foreground": "ff0000","background": "83000033","token": "source.ocaml support.other.module"},
{"foreground": "00fff8","token": "entity.name.type.variant"},
{"foreground": "00ff79","token": "source.ocaml entity.name.tag"},
{"foreground": "00ff79","token": "source.ocaml meta.record.definition"},
{"foreground": "ffffff","fontStyle": "bold","token": "punctuation.separator.parameters"},
{"foreground": "4c4c4c","background": "33333333","token": "meta.brace.pipe"},
{"foreground": "666666","fontStyle": "bold","token": "meta.brace.erb"},
{"foreground": "666666","fontStyle": "bold","token": "source.ruby.embedded.source.brace"},
{"foreground": "666666","fontStyle": "bold","token": "punctuation.section.dictionary"},
{"foreground": "666666","fontStyle": "bold","token": "punctuation.terminator.dictionary"},
{"foreground": "666666","fontStyle": "bold","token": "punctuation.separator.object"},
{"foreground": "666666","fontStyle": "bold","token": "punctuation.separator.statement"},
{"foreground": "666666","fontStyle": "bold","token": "punctuation.separator.key-value.css"},
{"foreground": "999999","fontStyle": "bold","token": "punctuation.section.scope.curly"},
{"foreground": "999999","fontStyle": "bold","token": "punctuation.section.scope"},
{"foreground": "0c823b","fontStyle": "bold","token": "punctuation.separator.objects"},
{"foreground": "0c823b","fontStyle": "bold","token": "meta.group.braces.curly meta.delimiter.object.comma"},
{"foreground": "0c823b","fontStyle": "bold","token": "punctuation.separator.key-value -meta.tag"},
{"foreground": "0c823b","fontStyle": "bold","token": "source.ocaml punctuation.separator.match-definition"},
{"foreground": "800043","token": "punctuation.separator.parameters.function.js"},
{"foreground": "800043","token": "punctuation.definition.function"},
{"foreground": "800043","token": "punctuation.separator.function-return"},
{"foreground": "800043","token": "punctuation.separator.function-definition"},
{"foreground": "800043","token": "punctuation.definition.arguments"},
{"foreground": "800043","token": "punctuation.separator.arguments"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "meta.group.braces.square punctuation.section.scope"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "meta.group.braces.square meta.delimiter.object.comma"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "meta.brace.square"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "punctuation.separator.array"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "punctuation.section.array"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "punctuation.definition.array"},
{"foreground": "7f5e40","background": "803d001a","fontStyle": "bold","token": "punctuation.definition.constant.range"},
{"background": "803d001a","token": "meta.structure.array -punctuation.definition.array"},
{"background": "803d001a","token": "meta.definition.range -punctuation.definition.constant.range"},
{"background": "00000080","token": "meta.brace.curly meta.group.css"},
{"foreground": "666666","background": "00000080","token": "meta.source.embedded"},
{"foreground": "666666","background": "00000080","token": "entity.other.django.tagbraces"},
{"background": "00000080","token": "source.ruby meta.even-tab"},
{"background": "00000080","token": "source.ruby meta.even-tab.group2"},
{"background": "00000080","token": "source.ruby meta.even-tab.group4"},
{"background": "00000080","token": "source.ruby meta.even-tab.group6"},
{"background": "00000080","token": "source.ruby meta.even-tab.group8"},
{"background": "00000080","token": "source.ruby meta.even-tab.group10"},
{"background": "00000080","token": "source.ruby meta.even-tab.group12"},
{"foreground": "666666","token": "meta.block.slate"},
{"foreground": "cccccc","token": "meta.block.content.slate"},
{"background": "0a0a0a","token": "meta.odd-tab.group1"},
{"background": "0a0a0a","token": "meta.group.braces"},
{"background": "0a0a0a","token": "meta.block.slate"},
{"background": "0a0a0a","token": "text.xml.strict meta.tag"},
{"background": "0a0a0a","token": "meta.paren-group"},
{"background": "0a0a0a","token": "meta.section"},
{"background": "0e0e0e","token": "meta.even-tab.group2"},
{"background": "0e0e0e","token": "meta.group.braces meta.group.braces"},
{"background": "0e0e0e","token": "meta.block.slate meta.block.slate"},
{"background": "0e0e0e","token": "text.xml.strict meta.tag meta.tag"},
{"background": "0e0e0e","token": "meta.group.braces meta.group.braces"},
{"background": "0e0e0e","token": "meta.paren-group meta.paren-group"},
{"background": "0e0e0e","token": "meta.section meta.section"},
{"background": "111111","token": "meta.odd-tab.group3"},
{"background": "111111","token": "meta.group.braces meta.group.braces meta.group.braces"},
{"background": "111111","token": "meta.block.slate meta.block.slate meta.block.slate"},
{"background": "111111","token": "text.xml.strict meta.tag meta.tag meta.tag"},
{"background": "111111","token": "meta.group.braces meta.group.braces meta.group.braces"},
{"background": "111111","token": "meta.paren-group meta.paren-group meta.paren-group"},
{"background": "111111","token": "meta.section meta.section meta.section"},
{"background": "151515","token": "meta.even-tab.group4"},
{"background": "151515","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "151515","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "151515","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag"},
{"background": "151515","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "151515","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "151515","token": "meta.section meta.section meta.section meta.section"},
{"background": "191919","token": "meta.odd-tab.group5"},
{"background": "191919","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "191919","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "191919","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "191919","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "191919","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "191919","token": "meta.section meta.section meta.section meta.section meta.section"},
{"background": "1c1c1c","token": "meta.even-tab.group6"},
{"background": "1c1c1c","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1c1c1c","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "1c1c1c","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "1c1c1c","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1c1c1c","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "1c1c1c","token": "meta.section meta.section meta.section meta.section meta.section meta.section"},
{"background": "1f1f1f","token": "meta.odd-tab.group7"},
{"background": "1f1f1f","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1f1f1f","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "1f1f1f","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "1f1f1f","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1f1f1f","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "1f1f1f","token": "meta.section meta.section meta.section meta.section meta.section meta.section meta.section"},
{"background": "212121","token": "meta.even-tab.group8"},
{"background": "212121","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "212121","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "212121","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "212121","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "212121","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "212121","token": "meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section"},
{"background": "242424","token": "meta.odd-tab.group9"},
{"background": "242424","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "242424","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "242424","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "242424","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "242424","token": "meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group meta.paren-group"},
{"background": "242424","token": "meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section meta.section"},
{"background": "1f1f1f","token": "meta.even-tab.group10"},
{"background": "151515","token": "meta.odd-tab.group11"},
{"foreground": "1b95e2","token": "meta.property.vendor.microsoft.trident.4"},
{"foreground": "1b95e2","token": "meta.property.vendor.microsoft.trident.4 support.type.property-name"},
{"foreground": "1b95e2","token": "meta.property.vendor.microsoft.trident.4 punctuation.terminator.rule"},
{"foreground": "f5c034","token": "meta.property.vendor.microsoft.trident.5"},
{"foreground": "f5c034","token": "meta.property.vendor.microsoft.trident.5 support.type.property-name"},
{"foreground": "f5c034","token": "meta.property.vendor.microsoft.trident.5 punctuation.separator.key-value"},
{"foreground": "f5c034","token": "meta.property.vendor.microsoft.trident.5 punctuation.terminator.rule"}
],
"colors": {
"editor.foreground": "#EEEEEE",
"editor.background": "#0D0D0DFA",
"editor.selectionBackground": "#0010B499",
"editor.lineHighlightBackground": "#00008033",
"editorCursor.foreground": "#3333FF",
"editorWhitespace.foreground": "#CCCCCC1A",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let brillianceDull = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "050505FA","token": ""},
{"foreground": "000000","background": "ffffff","fontStyle": "bold","token": "meta.thomas_aylott"},
{"foreground": "555555","background": "ffffff","fontStyle": "underline","token": "meta.subtlegradient"},
{"foreground": "e6e6e6","background": "ffffff","token": "meta.subtlegradient"},
{"foreground": "d2d1ab","background": "803d0033","token": "string -meta.tag -meta.doctype -string.regexp -string.literal -string.interpolated -string.quoted.literal -string.unquoted"},
{"foreground": "d2d1ab","background": "803d0033","token": "variable.parameter.misc.css"},
{"foreground": "d2d1ab","background": "803d0033","token": "text string source string"},
{"foreground": "d2d1ab","background": "803d0033","token": "string.unquoted string"},
{"foreground": "d2d1ab","background": "803d0033","token": "string.regexp string"},
{"foreground": "533f2c","token": "punctuation.definition.string -meta.tag"},
{"foreground": "fff80033","token": "string.regexp punctuation.definition.string"},
{"foreground": "fff80033","token": "string.quoted.literal punctuation.definition.string"},
{"foreground": "fff80033","token": "string.quoted.double.ruby.mod punctuation.definition.string"},
{"foreground": "a6a458","background": "43800033","token": "string.quoted.literal"},
{"foreground": "a6a458","background": "43800033","token": "string.quoted.double.ruby.mod"},
{"foreground": "d2beab","token": "string.unquoted -string.unquoted.embedded"},
{"foreground": "d2beab","token": "string.quoted.double.multiline"},
{"foreground": "d2beab","token": "meta.scope.heredoc"},
{"foreground": "d2d1ab","background": "1a1a1a","token": "string.interpolated"},
{"foreground": "a6a458","background": "43800033","token": "string.regexp"},
{"background": "43800033","token": "string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group string.regexp.group"},
{"foreground": "ffffff66","background": "43800033","token": "string.regexp.group string.regexp.group string.regexp.group string.regexp.group"},
{"foreground": "80a659","background": "43800033","token": "string.regexp.character-class"},
{"foreground": "56a5a4","background": "43800033","token": "string.regexp.arbitrary-repitition"},
{"foreground": "a75980","token": "source.regexp keyword.operator"},
{"foreground": "ffffff","fontStyle": "italic","token": "string.regexp comment"},
{"background": "0086ff33","token": "meta.group.assertion.regexp"},
{"foreground": "5780a6","token": "meta.assertion"},
{"foreground": "5780a6","token": "meta.group.assertion keyword.control.group.regexp"},
{"foreground": "95a658","token": "constant.numeric"},
{"foreground": "80a659","token": "constant.character"},
{"foreground": "59a559","token": "constant.language"},
{"foreground": "59a559","token": "keyword.other.unit"},
{"foreground": "59a559","token": "constant.other.java"},
{"foreground": "59a559","token": "constant.other.unit"},
{"foreground": "59a559","background": "04800033","token": "constant.language.pseudo-variable"},
{"foreground": "57a57d","token": "constant.other"},
{"foreground": "57a57d","token": "constant.block"},
{"foreground": "56a5a4","token": "support.constant"},
{"foreground": "56a5a4","token": "constant.name"},
{"foreground": "5e6b6b","token": "variable.language"},
{"foreground": "5e6b6b","token": "variable.other.readwrite.global.pre-defined"},
{"foreground": "56a5a4","token": "variable.other.constant"},
{"foreground": "56a5a4","background": "00807c33","token": "support.variable"},
{"foreground": "2b5252","background": "00438033","token": "variable.other.readwrite.global"},
{"foreground": "5780a6","token": "variable.other"},
{"foreground": "5780a6","token": "variable.js"},
{"foreground": "5780a6","background": "0007ff33","token": "variable.other.readwrite.class"},
{"foreground": "555f69","token": "variable.other.readwrite.instance"},
{"foreground": "555f69","token": "variable.other.php"},
{"foreground": "555f69","token": "variable.other.normal"},
{"foreground": "00000080","token": "punctuation.definition -punctuation.definition.comment"},
{"foreground": "00000080","token": "punctuation.separator.variable"},
{"foreground": "a77d58","token": "storage -storage.modifier"},
{"background": "803d0033","token": "other.preprocessor"},
{"background": "803d0033","token": "entity.name.preprocessor"},
{"foreground": "666666","token": "variable.language.this.js"},
{"foreground": "533f2c","token": "storage.modifier"},
{"foreground": "a7595a","token": "entity.name.class"},
{"foreground": "a7595a","token": "entity.name.type.class"},
{"foreground": "a7595a","token": "entity.name.type.module"},
{"foreground": "532d2d","background": "29161780","token": "meta.class -meta.class.instance"},
{"foreground": "532d2d","background": "29161780","token": "declaration.class"},
{"foreground": "532d2d","background": "29161780","token": "meta.definition.class"},
{"foreground": "532d2d","background": "29161780","token": "declaration.module"},
{"foreground": "a7595a","background": "80000433","token": "support.type"},
{"foreground": "a7595a","background": "80000433","token": "support.class"},
{"foreground": "a7595a","token": "entity.name.instance"},
{"background": "80004333","token": "meta.class.instance.constructor"},
{"foreground": "a75980","background": "80000433","token": "entity.other.inherited-class"},
{"foreground": "a75980","background": "80000433","token": "entity.name.module"},
{"foreground": "a75980","token": "object.property.function"},
{"foreground": "a75980","token": "meta.definition.method"},
{"foreground": "532d40","background": "80004333","token": "meta.function -(meta.tell-block)"},
{"foreground": "532d40","background": "80004333","token": "meta.property.function"},
{"foreground": "532d40","background": "80004333","token": "declaration.function"},
{"foreground": "a75980","token": "entity.name.function"},
{"foreground": "a75980","token": "entity.name.preprocessor"},
{"foreground": "a459a5","token": "keyword"},
{"foreground": "a459a5","background": "3c008033","token": "keyword.control"},
{"foreground": "8d809d","token": "keyword.other.special-method"},
{"foreground": "8d809d","token": "meta.function-call entity.name.function -(meta.function-call meta.function)"},
{"foreground": "8d809d","token": "support.function - variable"},
{"foreground": "634683","token": "support.function - variable"},
{"foreground": "7979b7","fontStyle": "bold","token": "keyword.operator"},
{"foreground": "7979b7","fontStyle": "bold","token": "declaration.function.operator"},
{"foreground": "7979b7","fontStyle": "bold","token": "meta.preprocessor.c.include"},
{"foreground": "9899c8","token": "keyword.operator.comparison"},
{"foreground": "abacd2","background": "3c008033","token": "variable.parameter -variable.parameter.misc.css"},
{"foreground": "abacd2","background": "3c008033","token": "meta.definition.method meta.definition.param-list"},
{"foreground": "abacd2","background": "3c008033","token": "meta.function.method.with-arguments variable.parameter.function"},
{"foreground": "cdcdcd","background": "333333","token": "meta.doctype"},
{"foreground": "cdcdcd","background": "333333","token": "meta.tag.sgml-declaration.doctype"},
{"foreground": "cdcdcd","background": "333333","token": "meta.tag.sgml.doctype"},
{"foreground": "333333","token": "meta.tag"},
{"foreground": "666666","background": "333333bf","token": "meta.tag.structure"},
{"foreground": "666666","background": "333333bf","token": "meta.tag.segment"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.block"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.xml"},
{"foreground": "4c4c4c","background": "4c4c4c33","token": "meta.tag.key"},
{"foreground": "a77d58","background": "803d0033","token": "meta.tag.inline"},
{"background": "803d0033","token": "meta.tag.inline source"},
{"foreground": "a7595a","background": "80000433","token": "meta.tag.other"},
{"foreground": "a7595a","background": "80000433","token": "entity.name.tag.style"},
{"foreground": "a7595a","background": "80000433","token": "source entity.other.attribute-name -text.html.basic.embedded"},
{"foreground": "a7595a","background": "80000433","token": "entity.name.tag.script"},
{"foreground": "a7595a","background": "80000433","token": "meta.tag.block.script"},
{"foreground": "5780a6","background": "00438033","token": "meta.tag.form"},
{"foreground": "5780a6","background": "00438033","token": "meta.tag.block.form"},
{"foreground": "a459a5","background": "3c008033","token": "meta.tag.meta"},
{"background": "121212","token": "meta.section.html.head"},
{"background": "0043801a","token": "meta.section.html.form"},
{"foreground": "666666","token": "meta.tag.xml"},
{"foreground": "ffffff4d","token": "entity.name.tag"},
{"foreground": "ffffff33","token": "entity.other.attribute-name"},
{"foreground": "ffffff33","token": "meta.tag punctuation.definition.string"},
{"foreground": "ffffff66","token": "meta.tag string -source -punctuation"},
{"foreground": "ffffff66","token": "text source text meta.tag string -punctuation"},
{"foreground": "a6a458","background": "33333333","token": "markup markup -(markup meta.paragraph.list)"},
{"foreground": "000000","background": "ffffff","token": "markup.hr"},
{"foreground": "666666","background": "33333380","token": "markup.heading"},
{"fontStyle": "bold","token": "markup.bold"},
{"fontStyle": "italic","token": "markup.italic"},
{"fontStyle": "underline","token": "markup.underline"},
{"foreground": "5780a6","token": "meta.reference"},
{"foreground": "5780a6","token": "markup.underline.link"},
{"foreground": "56a5a4","background": "00438033","token": "entity.name.reference"},
{"foreground": "56a5a4","fontStyle": "underline","token": "meta.reference.list markup.underline.link"},
{"foreground": "56a5a4","fontStyle": "underline","token": "text.html.textile markup.underline.link"},
{"foreground": "999999","background": "000000","token": "markup.raw.block"},
{"background": "ffffff1a","token": "markup.quote"},
{"foreground": "666666","background": "00000080","token": "meta.selector"},
{"foreground": "575aa6","background": "00048033","token": "meta.attribute-match.css"},
{"foreground": "7c58a5","token": "entity.other.attribute-name.pseudo-class"},
{"foreground": "7c58a5","token": "entity.other.attribute-name.tag.pseudo-class"},
{"foreground": "a459a5","token": "meta.selector entity.other.attribute-name.class"},
{"foreground": "a75980","token": "meta.selector entity.other.attribute-name.id"},
{"foreground": "a7595a","token": "meta.selector entity.name.tag"},
{"foreground": "a77d58","fontStyle": "bold","token": "entity.name.tag.wildcard"},
{"foreground": "a77d58","fontStyle": "bold","token": "entity.other.attribute-name.universal"},
{"foreground": "333333","fontStyle": "bold","token": "meta.scope.property-list"},
{"foreground": "999999","token": "meta.property-name"},
{"foreground": "ffffff","background": "000000","token": "support.type.property-name"},
{"foreground": "999999","background": "0d0d0d","token": "meta.property-value"},
{"background": "000000","token": "text.latex markup.raw"},
{"foreground": "bdabd1","token": "text.latex support.function -support.function.textit -support.function.emph"},
{"foreground": "ffffffbf","token": "text.latex support.function.section"},
{"foreground": "000000","background": "ffffff","token": "text.latex entity.name.section -meta.group -keyword.operator.braces"},
{"background": "00000080","token": "text.latex keyword.operator.delimiter"},
{"foreground": "999999","token": "text.latex keyword.operator.brackets"},
{"foreground": "666666","token": "text.latex keyword.operator.braces"},
{"foreground": "0008ff4d","background": "00048033","token": "meta.footnote"},
{"background": "ffffff0d","token": "text.latex meta.label.reference"},
{"foreground": "a7595a","background": "180d0c","token": "text.latex keyword.control.ref"},
{"foreground": "d2beab","background": "291616","token": "text.latex variable.parameter.label.reference"},
{"foreground": "a75980","background": "180d12","token": "text.latex keyword.control.cite"},
{"foreground": "e8d5de","background": "29161f","token": "variable.parameter.cite"},
{"foreground": "ffffff80","token": "text.latex variable.parameter.label"},
{"foreground": "33333333","token": "text.latex meta.group.braces"},
{"foreground": "33333333","background": "00000080","token": "text.latex meta.environment.list"},
{"foreground": "33333333","background": "00000080","token": "text.latex meta.environment.list meta.environment.list"},
{"foreground": "33333333","background": "000000","token": "text.latex meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "33333333","token": "text.latex meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list meta.environment.list"},
{"foreground": "000000","background": "cdcdcd","token": "text.latex meta.end-document"},
{"foreground": "000000","background": "cdcdcd","token": "text.latex meta.begin-document"},
{"foreground": "000000","background": "cdcdcd","token": "meta.end-document.latex support.function"},
{"foreground": "000000","background": "cdcdcd","token": "meta.end-document.latex variable.parameter"},
{"foreground": "000000","background": "cdcdcd","token": "meta.begin-document.latex support.function"},
{"foreground": "000000","background": "cdcdcd","token": "meta.begin-document.latex variable.parameter"},
{"foreground": "596b61","background": "45815d33","token": "meta.brace.erb.return-value"},
{"background": "66666633","token": "source.ruby.rails.embedded.return-value.one-line"},
{"foreground": "56a5a4","background": "00fff81a","token": "punctuation.section.embedded -(source string source punctuation.section.embedded)"},
{"foreground": "56a5a4","background": "00fff81a","token": "meta.brace.erb.html"},
{"background": "00fff81a","token": "source.ruby.rails.embedded.one-line"},
{"foreground": "555f69","token": "source string source punctuation.section.embedded"},
{"background": "000000","token": "source"},
{"background": "000000","token": "meta.brace.erb"},
{"foreground": "ffffff","background": "33333380","token": "source string source"},
{"foreground": "999999","background": "00000099","token": "source string.interpolated source"},
{"background": "3333331a","token": "source.java.embedded"},
{"foreground": "ffffff","token": "text -text.xml.strict"},
{"foreground": "cccccc","background": "000000","token": "text source"},
{"foreground": "cccccc","background": "000000","token": "meta.scope.django.template"},
{"foreground": "999999","token": "text string source"},
{"foreground": "333333","token": "meta.syntax"},
{"foreground": "211211","background": "a7595a","fontStyle": "bold","token": "invalid"},
{"foreground": "8f8fc3","background": "0000ff1a","fontStyle": "italic","token": "0comment"},
{"foreground": "0000ff1a","fontStyle": "bold","token": "comment punctuation"},
{"foreground": "333333","token": "comment"},
{"foreground": "262626","background": "8080800d","fontStyle": "bold italic","token": "comment punctuation"},
{"fontStyle": "italic","token": "text comment.block -source"},
{"foreground": "81bb9e","background": "15281f","token": "markup.inserted"},
{"foreground": "bc839f","background": "400021","token": "markup.deleted"},
{"foreground": "c3c38f","background": "533f2c","token": "markup.changed"},
{"foreground": "ffffff","background": "000000","token": "text.subversion-commit meta.scope.changed-files"},
{"foreground": "ffffff","background": "000000","token": "text.subversion-commit meta.scope.changed-files.svn meta.diff.separator"},
{"foreground": "000000","background": "ffffff","token": "text.subversion-commit"},
{"foreground": "ffffff","background": "ffffff03","fontStyle": "bold","token": "punctuation.terminator"},
{"foreground": "ffffff","background": "ffffff03","fontStyle": "bold","token": "meta.delimiter"},
{"foreground": "ffffff","background": "ffffff03","fontStyle": "bold","token": "punctuation.separator.method"},
{"background": "000000bf","token": "punctuation.terminator.statement"},
{"background": "000000bf","token": "meta.delimiter.statement.js"},
{"background": "00000040","token": "meta.delimiter.object.js"},
{"foreground": "533f2c","fontStyle": "bold","token": "string.quoted.single.brace"},
{"foreground": "533f2c","fontStyle": "bold","token": "string.quoted.double.brace"},
{"background": "ffffff","token": "text.blog -(text.blog text)"},
{"foreground": "666666","background": "ffffff","token": "meta.headers.blog"},
{"foreground": "192b2a","background": "00fff81a","token": "meta.headers.blog keyword.other.blog"},
{"foreground": "533f2c","background": "ffff551a","token": "meta.headers.blog string.unquoted.blog"},
{"foreground": "4c4c4c","background": "33333333","token": "meta.brace.pipe"},
{"foreground": "4c4c4c","fontStyle": "bold","token": "meta.brace.erb"},
{"foreground": "4c4c4c","fontStyle": "bold","token": "source.ruby.embedded.source.brace"},
{"foreground": "4c4c4c","fontStyle": "bold","token": "punctuation.section.dictionary"},
{"foreground": "4c4c4c","fontStyle": "bold","token": "punctuation.terminator.dictionary"},
{"foreground": "4c4c4c","fontStyle": "bold","token": "punctuation.separator.object"},
{"foreground": "ffffff","fontStyle": "bold","token": "meta.group.braces.curly punctuation.section.scope"},
{"foreground": "ffffff","fontStyle": "bold","token": "meta.brace.curly"},
{"foreground": "345743","fontStyle": "bold","token": "punctuation.separator.objects"},
{"foreground": "345743","fontStyle": "bold","token": "meta.group.braces.curly meta.delimiter.object.comma"},
{"foreground": "345743","fontStyle": "bold","token": "punctuation.separator.key-value -meta.tag"},
{"foreground": "695f55","background": "803d001a","fontStyle": "bold","token": "meta.group.braces.square punctuation.section.scope"},
{"foreground": "695f55","background": "803d001a","fontStyle": "bold","token": "meta.group.braces.square meta.delimiter.object.comma"},
{"foreground": "695f55","background": "803d001a","fontStyle": "bold","token": "meta.brace.square"},
{"foreground": "695f55","background": "803d001a","fontStyle": "bold","token": "punctuation.separator.array"},
{"foreground": "695f55","background": "803d001a","fontStyle": "bold","token": "punctuation.section.array"},
{"foreground": "cdcdcd","background": "00000080","token": "meta.brace.curly meta.group"},
{"foreground": "532d40","fontStyle": "bold","token": "meta.group.braces.round punctuation.section.scope"},
{"foreground": "532d40","fontStyle": "bold","token": "meta.group.braces.round meta.delimiter.object.comma"},
{"foreground": "532d40","fontStyle": "bold","token": "meta.brace.round"},
{"foreground": "abacd2","background": "3c008033","token": "punctuation.section.function"},
{"foreground": "abacd2","background": "3c008033","token": "meta.brace.curly.function"},
{"foreground": "abacd2","background": "3c008033","token": "meta.function-call punctuation.section.scope.ruby"},
{"foreground": "666666","background": "00000080","token": "meta.source.embedded"},
{"foreground": "666666","background": "00000080","token": "entity.other.django.tagbraces"},
{"background": "0a0a0a","token": "meta.odd-tab.group1"},
{"background": "0a0a0a","token": "meta.group.braces"},
{"background": "0a0a0a","token": "meta.block.slate"},
{"background": "0a0a0a","token": "text.xml.strict meta.tag"},
{"background": "0a0a0a","token": "meta.tell-block meta.tell-block"},
{"background": "0e0e0e","token": "meta.even-tab.group2"},
{"background": "0e0e0e","token": "meta.group.braces meta.group.braces"},
{"background": "0e0e0e","token": "meta.block.slate meta.block.slate"},
{"background": "0e0e0e","token": "text.xml.strict meta.tag meta.tag"},
{"background": "0e0e0e","token": "meta.group.braces meta.group.braces"},
{"background": "0e0e0e","token": "meta.tell-block meta.tell-block"},
{"background": "111111","token": "meta.odd-tab.group3"},
{"background": "111111","token": "meta.group.braces meta.group.braces meta.group.braces"},
{"background": "111111","token": "meta.block.slate meta.block.slate meta.block.slate"},
{"background": "111111","token": "text.xml.strict meta.tag meta.tag meta.tag"},
{"background": "111111","token": "meta.group.braces meta.group.braces meta.group.braces"},
{"background": "111111","token": "meta.tell-block meta.tell-block meta.tell-block"},
{"background": "151515","token": "meta.even-tab.group4"},
{"background": "151515","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "151515","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "151515","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag"},
{"background": "151515","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "151515","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"background": "191919","token": "meta.odd-tab.group5"},
{"background": "191919","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "191919","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "191919","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "191919","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "191919","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"background": "1c1c1c","token": "meta.even-tab.group6"},
{"background": "1c1c1c","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1c1c1c","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "1c1c1c","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "1c1c1c","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1c1c1c","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"background": "1f1f1f","token": "meta.odd-tab.group7"},
{"background": "1f1f1f","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1f1f1f","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "1f1f1f","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "1f1f1f","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "1f1f1f","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"background": "212121","token": "meta.even-tab.group8"},
{"background": "212121","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "212121","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "212121","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "212121","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "212121","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"background": "242424","token": "meta.odd-tab.group11"},
{"background": "242424","token": "meta.odd-tab.group10"},
{"background": "242424","token": "meta.odd-tab.group9"},
{"background": "242424","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "242424","token": "meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate meta.block.slate"},
{"background": "242424","token": "text.xml.strict meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag meta.tag"},
{"background": "242424","token": "meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces meta.group.braces"},
{"background": "242424","token": "meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block meta.tell-block"},
{"foreground": "666666","token": "meta.block.slate"},
{"foreground": "cdcdcd","token": "meta.block.content.slate"}
],
"colors": {
"editor.foreground": "#CDCDCD",
"editor.background": "#050505FA",
"editor.selectionBackground": "#2E2EE64D",
"editor.lineHighlightBackground": "#0000801A",
"editorCursor.foreground": "#7979B7",
"editorWhitespace.foreground": "#CDCDCD1A",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let chromeDevTools = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "FFFFFF","token": ""},
{"foreground": "c41a16","token": "string"},
{"foreground": "1c00cf","token": "constant.numeric"},
{"foreground": "aa0d91","token": "keyword"},
{"foreground": "000000","token": "keyword.operator"},
{"foreground": "aa0d91","token": "constant.language"},
{"foreground": "990000","token": "support.class.exception"},
{"foreground": "000000","token": "entity.name.function"},
{"fontStyle": "bold underline","token": "entity.name.type"},
{"fontStyle": "italic","token": "variable.parameter"},
{"foreground": "007400","token": "comment"},
{"foreground": "ff0000","token": "invalid"},
{"background": "e71a1100","token": "invalid.deprecated.trailing-whitespace"},
{"foreground": "000000","background": "fafafafc","token": "text source"},
{"foreground": "aa0d91","token": "meta.tag"},
{"foreground": "aa0d91","token": "declaration.tag"},
{"foreground": "000000","fontStyle": "bold","token": "support"},
{"foreground": "aa0d91","token": "storage"},
{"fontStyle": "bold underline","token": "entity.name.section"},
{"foreground": "000000","fontStyle": "bold","token": "entity.name.function.frame"},
{"foreground": "333333","token": "meta.tag.preprocessor.xml"},
{"foreground": "994500","fontStyle": "italic","token": "entity.other.attribute-name"},
{"foreground": "881280","token": "entity.name.tag"}
],
"colors": {
"editor.foreground": "#000000",
"editor.background": "#FFFFFF",
"editor.selectionBackground": "#BAD6FD",
"editor.lineHighlightBackground": "#0000001A",
"editorCursor.foreground": "#000000",
"editorWhitespace.foreground": "#B3B3B3F4",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let cloudsMidnight = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "191919","token": ""},
{"foreground": "3c403b","token": "comment"},
{"foreground": "5d90cd","token": "string"},
{"foreground": "46a609","token": "constant.numeric"},
{"foreground": "39946a","token": "constant.language"},
{"foreground": "927c5d","token": "keyword"},
{"foreground": "927c5d","token": "support.constant.property-value"},
{"foreground": "927c5d","token": "constant.other.color"},
{"foreground": "366f1a","token": "keyword.other.unit"},
{"foreground": "a46763","token": "entity.other.attribute-name.html"},
{"foreground": "4b4b4b","token": "keyword.operator"},
{"foreground": "e92e2e","token": "storage"},
{"foreground": "858585","token": "entity.other.inherited-class"},
{"foreground": "606060","token": "entity.name.tag"},
{"foreground": "a165ac","token": "constant.character.entity"},
{"foreground": "a165ac","token": "support.class.js"},
{"foreground": "606060","token": "entity.other.attribute-name"},
{"foreground": "e92e2e","token": "meta.selector.css"},
{"foreground": "e92e2e","token": "entity.name.tag.css"},
{"foreground": "e92e2e","token": "entity.other.attribute-name.id.css"},
{"foreground": "e92e2e","token": "entity.other.attribute-name.class.css"},
{"foreground": "616161","token": "meta.property-name.css"},
{"foreground": "e92e2e","token": "support.function"},
{"foreground": "ffffff","background": "e92e2e","token": "invalid"},
{"foreground": "e92e2e","token": "punctuation.section.embedded"},
{"foreground": "606060","token": "punctuation.definition.tag"},
{"foreground": "a165ac","token": "constant.other.color.rgb-value.css"},
{"foreground": "a165ac","token": "support.constant.property-value.css"}
],
"colors": {
"editor.foreground": "#929292",
"editor.background": "#191919",
"editor.selectionBackground": "#000000",
"editor.lineHighlightBackground": "#D7D7D708",
"editorCursor.foreground": "#7DA5DC",
"editorWhitespace.foreground": "#BFBFBF",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let clouds = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "FFFFFF","token": ""},
{"foreground": "bcc8ba","token": "comment"},
{"foreground": "5d90cd","token": "string"},
{"foreground": "46a609","token": "constant.numeric"},
{"foreground": "39946a","token": "constant.language"},
{"foreground": "af956f","token": "keyword"},
{"foreground": "af956f","token": "support.constant.property-value"},
{"foreground": "af956f","token": "constant.other.color"},
{"foreground": "96dc5f","token": "keyword.other.unit"},
{"foreground": "484848","token": "keyword.operator"},
{"foreground": "c52727","token": "storage"},
{"foreground": "858585","token": "entity.other.inherited-class"},
{"foreground": "606060","token": "entity.name.tag"},
{"foreground": "bf78cc","token": "constant.character.entity"},
{"foreground": "bf78cc","token": "support.class.js"},
{"foreground": "606060","token": "entity.other.attribute-name"},
{"foreground": "c52727","token": "meta.selector.css"},
{"foreground": "c52727","token": "entity.name.tag.css"},
{"foreground": "c52727","token": "entity.other.attribute-name.id.css"},
{"foreground": "c52727","token": "entity.other.attribute-name.class.css"},
{"foreground": "484848","token": "meta.property-name.css"},
{"foreground": "c52727","token": "support.function"},
{"background": "ff002a","token": "invalid"},
{"foreground": "c52727","token": "punctuation.section.embedded"},
{"foreground": "606060","token": "punctuation.definition.tag"},
{"foreground": "bf78cc","token": "constant.other.color.rgb-value.css"},
{"foreground": "bf78cc","token": "support.constant.property-value.css"}
],
"colors": {
"editor.foreground": "#000000",
"editor.background": "#FFFFFF",
"editor.selectionBackground": "#BDD5FC",
"editor.lineHighlightBackground": "#FFFBD1",
"editorCursor.foreground": "#000000",
"editorWhitespace.foreground": "#BFBFBF",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let cobalt = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "002240","token": ""},
{"foreground": "e1efff","token": "punctuation - (punctuation.definition.string || punctuation.definition.comment)"},
{"foreground": "ff628c","token": "constant"},
{"foreground": "ffdd00","token": "entity"},
{"foreground": "ff9d00","token": "keyword"},
{"foreground": "ffee80","token": "storage"},
{"foreground": "3ad900","token": "string -string.unquoted.old-plist -string.unquoted.heredoc"},
{"foreground": "3ad900","token": "string.unquoted.heredoc string"},
{"foreground": "0088ff","fontStyle": "italic","token": "comment"},
{"foreground": "80ffbb","token": "support"},
{"foreground": "cccccc","token": "variable"},
{"foreground": "ff80e1","token": "variable.language"},
{"foreground": "ffee80","token": "meta.function-call"},
{"foreground": "f8f8f8","background": "800f00","token": "invalid"},
{"foreground": "ffffff","background": "223545","token": "text source"},
{"foreground": "ffffff","background": "223545","token": "string.unquoted.heredoc"},
{"foreground": "ffffff","background": "223545","token": "source source"},
{"foreground": "80fcff","fontStyle": "italic","token": "entity.other.inherited-class"},
{"foreground": "9eff80","token": "string.quoted source"},
{"foreground": "80ff82","token": "string constant"},
{"foreground": "80ffc2","token": "string.regexp"},
{"foreground": "edef7d","token": "string variable"},
{"foreground": "ffb054","token": "support.function"},
{"foreground": "eb939a","token": "support.constant"},
{"foreground": "ff1e00","token": "support.type.exception"},
{"foreground": "8996a8","token": "meta.preprocessor.c"},
{"foreground": "afc4db","token": "meta.preprocessor.c keyword"},
{"foreground": "73817d","token": "meta.sgml.html meta.doctype"},
{"foreground": "73817d","token": "meta.sgml.html meta.doctype entity"},
{"foreground": "73817d","token": "meta.sgml.html meta.doctype string"},
{"foreground": "73817d","token": "meta.xml-processing"},
{"foreground": "73817d","token": "meta.xml-processing entity"},
{"foreground": "73817d","token": "meta.xml-processing string"},
{"foreground": "9effff","token": "meta.tag"},
{"foreground": "9effff","token": "meta.tag entity"},
{"foreground": "9effff","token": "meta.selector.css entity.name.tag"},
{"foreground": "ffb454","token": "meta.selector.css entity.other.attribute-name.id"},
{"foreground": "5fe461","token": "meta.selector.css entity.other.attribute-name.class"},
{"foreground": "9df39f","token": "support.type.property-name.css"},
{"foreground": "f6f080","token": "meta.property-group support.constant.property-value.css"},
{"foreground": "f6f080","token": "meta.property-value support.constant.property-value.css"},
{"foreground": "f6aa11","token": "meta.preprocessor.at-rule keyword.control.at-rule"},
{"foreground": "edf080","token": "meta.property-value support.constant.named-color.css"},
{"foreground": "edf080","token": "meta.property-value constant"},
{"foreground": "eb939a","token": "meta.constructor.argument.css"},
{"foreground": "f8f8f8","background": "000e1a","token": "meta.diff"},
{"foreground": "f8f8f8","background": "000e1a","token": "meta.diff.header"},
{"foreground": "f8f8f8","background": "4c0900","token": "markup.deleted"},
{"foreground": "f8f8f8","background": "806f00","token": "markup.changed"},
{"foreground": "f8f8f8","background": "154f00","token": "markup.inserted"},
{"background": "8fddf630","token": "markup.raw"},
{"background": "004480","token": "markup.quote"},
{"background": "130d26","token": "markup.list"},
{"foreground": "c1afff","fontStyle": "bold","token": "markup.bold"},
{"foreground": "b8ffd9","fontStyle": "italic","token": "markup.italic"},
{"foreground": "c8e4fd","background": "001221","fontStyle": "bold","token": "markup.heading"}
],
"colors": {
"editor.foreground": "#FFFFFF",
"editor.background": "#002240",
"editor.selectionBackground": "#B36539BF",
"editor.lineHighlightBackground": "#00000059",
"editorCursor.foreground": "#FFFFFF",
"editorWhitespace.foreground": "#FFFFFF26",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let dracula = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "282a36","token": ""},
{"foreground": "6272a4","token": "comment"},
{"foreground": "f1fa8c","token": "string"},
{"foreground": "bd93f9","token": "constant.numeric"},
{"foreground": "bd93f9","token": "constant.language"},
{"foreground": "bd93f9","token": "constant.character"},
{"foreground": "bd93f9","token": "constant.other"},
{"foreground": "ffb86c","token": "variable.other.readwrite.instance"},
{"foreground": "ff79c6","token": "constant.character.escaped"},
{"foreground": "ff79c6","token": "constant.character.escape"},
{"foreground": "ff79c6","token": "string source"},
{"foreground": "ff79c6","token": "string source.ruby"},
{"foreground": "ff79c6","token": "keyword"},
{"foreground": "ff79c6","token": "storage"},
{"foreground": "8be9fd","fontStyle": "italic","token": "storage.type"},
{"foreground": "50fa7b","fontStyle": "underline","token": "entity.name.class"},
{"foreground": "50fa7b","fontStyle": "italic underline","token": "entity.other.inherited-class"},
{"foreground": "50fa7b","token": "entity.name.function"},
{"foreground": "ffb86c","fontStyle": "italic","token": "variable.parameter"},
{"foreground": "ff79c6","token": "entity.name.tag"},
{"foreground": "50fa7b","token": "entity.other.attribute-name"},
{"foreground": "8be9fd","token": "support.function"},
{"foreground": "6be5fd","token": "support.constant"},
{"foreground": "66d9ef","fontStyle": " italic","token": "support.type"},
{"foreground": "66d9ef","fontStyle": " italic","token": "support.class"},
{"foreground": "f8f8f0","background": "ff79c6","token": "invalid"},
{"foreground": "f8f8f0","background": "bd93f9","token": "invalid.deprecated"},
{"foreground": "cfcfc2","token": "meta.structure.dictionary.json string.quoted.double.json"},
{"foreground": "6272a4","token": "meta.diff"},
{"foreground": "6272a4","token": "meta.diff.header"},
{"foreground": "ff79c6","token": "markup.deleted"},
{"foreground": "50fa7b","token": "markup.inserted"},
{"foreground": "e6db74","token": "markup.changed"},
{"foreground": "bd93f9","token": "constant.numeric.line-number.find-in-files - match"},
{"foreground": "e6db74","token": "entity.name.filename"},
{"foreground": "f83333","token": "message.error"},
{"foreground": "eeeeee","token": "punctuation.definition.string.begin.json - meta.structure.dictionary.value.json"},
{"foreground": "eeeeee","token": "punctuation.definition.string.end.json - meta.structure.dictionary.value.json"},
{"foreground": "8be9fd","token": "meta.structure.dictionary.json string.quoted.double.json"},
{"foreground": "f1fa8c","token": "meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "50fa7b","token": "meta meta meta meta meta meta meta.structure.dictionary.value string"},
{"foreground": "ffb86c","token": "meta meta meta meta meta meta.structure.dictionary.value string"},
{"foreground": "ff79c6","token": "meta meta meta meta meta.structure.dictionary.value string"},
{"foreground": "bd93f9","token": "meta meta meta meta.structure.dictionary.value string"},
{"foreground": "50fa7b","token": "meta meta meta.structure.dictionary.value string"},
{"foreground": "ffb86c","token": "meta meta.structure.dictionary.value string"}
],
"colors": {
"editor.foreground": "#f8f8f2",
"editor.background": "#282a36",
"editor.selectionBackground": "#44475a",
"editor.lineHighlightBackground": "#44475a",
"editorCursor.foreground": "#f8f8f0",
"editorWhitespace.foreground": "#3B3A32",
"editorIndentGuide.activeBackground": "#9D550FB0",
"editor.selectionHighlightBorder": "#222218",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let dreamweaver = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "FFFFFF","token": ""},
{"foreground": "000000","token": "text"},
{"foreground": "ee000b","token": "constant.numeric - source.css"},
{"foreground": "9a9a9a","token": "comment"},
{"foreground": "00359e","token": "text.html meta.tag"},
{"foreground": "001eff","token": "text.html.basic meta.tag string.quoted - source"},
{"foreground": "000000","fontStyle": "bold","token": "text.html.basic constant.character.entity.html"},
{"foreground": "106800","token": "text.html meta.tag.a - string"},
{"foreground": "6d232e","token": "text.html meta.tag.img - string"},
{"foreground": "ff9700","token": "text.html meta.tag.form - string"},
{"foreground": "009079","token": "text.html meta.tag.table - string"},
{"foreground": "842b44","token": "source.js.embedded.html punctuation.definition.tag - source.php"},
{"foreground": "842b44","token": "source.js.embedded.html entity.name.tag.script"},
{"foreground": "842b44","token": "source.js.embedded entity.other.attribute-name - source.js string"},
{"foreground": "9a9a9a","token": "source.js comment - source.php"},
{"foreground": "000000","token": "source.js meta.function - source.php"},
{"foreground": "24c696","token": "source.js meta.class - source.php"},
{"foreground": "24c696","token": "source.js support.function - source.php"},
{"foreground": "0035ff","token": "source.js string - source.php"},
{"foreground": "0035ff","token": "source.js keyword.operator"},
{"foreground": "7e00b7","token": "source.js support.class"},
{"foreground": "000000","fontStyle": "bold","token": "source.js storage"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js storage - storage.type.function - source.php"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js constant - source.php"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js keyword - source.php"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js variable.language"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js meta.brace"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js punctuation.definition.parameters.begin"},
{"foreground": "05208c","fontStyle": "bold","token": "source.js punctuation.definition.parameters.end"},
{"foreground": "106800","token": "source.js string.regexp"},
{"foreground": "106800","token": "source.js string.regexp constant"},
{"foreground": "8d00b7","token": "source.css.embedded.html punctuation.definition.tag"},
{"foreground": "8d00b7","token": "source.css.embedded.html entity.name.tag.style"},
{"foreground": "8d00b7","token": "source.css.embedded entity.other.attribute-name - meta.selector"},
{"foreground": "009c7f","fontStyle": "bold","token": "source.css meta.at-rule.import.css"},
{"foreground": "ee000b","fontStyle": "bold","token": "source.css keyword.other.important"},
{"foreground": "430303","fontStyle": "bold","token": "source.css meta.at-rule.media"},
{"foreground": "106800","token": "source.css string"},
{"foreground": "da29ff","token": "source.css meta.selector"},
{"foreground": "da29ff","token": "source.css meta.property-list"},
{"foreground": "da29ff","token": "source.css meta.at-rule"},
{"foreground": "da29ff","fontStyle": "bold","token": "source.css punctuation.separator - source.php"},
{"foreground": "da29ff","fontStyle": "bold","token": "source.css punctuation.terminator - source.php"},
{"foreground": "05208c","token": "source.css meta.property-name"},
{"foreground": "0035ff","token": "source.css meta.property-value"},
{"foreground": "ee000b","fontStyle": "bold","token": "source.php punctuation.section.embedded.begin"},
{"foreground": "ee000b","fontStyle": "bold","token": "source.php punctuation.section.embedded.end"},
{"foreground": "000000","token": "source.php - punctuation.section"},
{"foreground": "000000","token": "source.php variable"},
{"foreground": "000000","token": "source.php meta.function.arguments"},
{"foreground": "05208c","token": "source.php punctuation - string - variable - meta.function"},
{"foreground": "24bf96","token": "source.php storage.type"},
{"foreground": "009714","token": "source.php keyword - comment"},
{"foreground": "009714","token": "source.php storage.type.class"},
{"foreground": "009714","token": "source.php storage.type.interface"},
{"foreground": "009714","token": "source.php storage.modifier"},
{"foreground": "009714","token": "source.php constant.language"},
{"foreground": "0035ff","token": "source.php support"},
{"foreground": "0035ff","token": "source.php storage"},
{"foreground": "0035ff","token": "source.php keyword.operator"},
{"foreground": "0035ff","token": "source.php storage.type.function"},
{"foreground": "0092f2","token": "source.php variable.other.global"},
{"foreground": "551d02","token": "source.php support.constant"},
{"foreground": "551d02","token": "source.php constant.language.php"},
{"foreground": "e20000","token": "source.php string"},
{"foreground": "e20000","token": "source.php string keyword.operator"},
{"foreground": "ff6200","token": "source.php string.quoted.double variable"},
{"foreground": "ff9404","token": "source.php comment"},
{"foreground": "ee000b","background": "efff8a","fontStyle": "bold","token": "invalid"}
],
"colors": {
"editor.foreground": "#000000",
"editor.background": "#FFFFFF",
"editor.selectionBackground": "#5EA0FF",
"editor.lineHighlightBackground": "#00000012",
"editorCursor.foreground": "#000000",
"editorWhitespace.foreground": "#BFBFBF",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let espressoLibre = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "2A211C","token": ""},
{"foreground": "0066ff","fontStyle": "italic","token": "comment"},
{"foreground": "43a8ed","fontStyle": "bold","token": "keyword"},
{"foreground": "43a8ed","fontStyle": "bold","token": "storage"},
{"foreground": "44aa43","token": "constant.numeric"},
{"foreground": "c5656b","fontStyle": "bold","token": "constant"},
{"foreground": "585cf6","fontStyle": "bold","token": "constant.language"},
{"foreground": "318495","token": "variable.language"},
{"foreground": "318495","token": "variable.other"},
{"foreground": "049b0a","token": "string"},
{"foreground": "2fe420","token": "constant.character.escape"},
{"foreground": "2fe420","token": "string source"},
{"foreground": "1a921c","token": "meta.preprocessor"},
{"foreground": "9aff87","fontStyle": "bold","token": "keyword.control.import"},
{"foreground": "ff9358","fontStyle": "bold","token": "entity.name.function"},
{"foreground": "ff9358","fontStyle": "bold","token": "keyword.other.name-of-parameter.objc"},
{"fontStyle": "underline","token": "entity.name.type"},
{"fontStyle": "italic","token": "entity.other.inherited-class"},
{"fontStyle": "italic","token": "variable.parameter"},
{"foreground": "8b8e9c","token": "storage.type.method"},
{"fontStyle": "italic","token": "meta.section entity.name.section"},
{"fontStyle": "italic","token": "declaration.section entity.name.section"},
{"foreground": "7290d9","fontStyle": "bold","token": "support.function"},
{"foreground": "6d79de","fontStyle": "bold","token": "support.class"},
{"foreground": "6d79de","fontStyle": "bold","token": "support.type"},
{"foreground": "00af0e","fontStyle": "bold","token": "support.constant"},
{"foreground": "2f5fe0","fontStyle": "bold","token": "support.variable"},
{"foreground": "687687","token": "keyword.operator.js"},
{"foreground": "ffffff","background": "990000","token": "invalid"},
{"background": "ffd0d0","token": "invalid.deprecated.trailing-whitespace"},
{"background": "f5aa7730","token": "text source"},
{"background": "f5aa7730","token": "string.unquoted"},
{"foreground": "8f7e65","token": "meta.tag.preprocessor.xml"},
{"foreground": "888888","token": "meta.tag.sgml.doctype"},
{"fontStyle": "italic","token": "string.quoted.docinfo.doctype.DTD"},
{"foreground": "43a8ed","token": "meta.tag"},
{"foreground": "43a8ed","token": "declaration.tag"},
{"fontStyle": "bold","token": "entity.name.tag"},
{"fontStyle": "italic","token": "entity.other.attribute-name"}
],
"colors": {
"editor.foreground": "#BDAE9D",
"editor.background": "#2A211C",
"editor.selectionBackground": "#C3DCFF",
"editor.lineHighlightBackground": "#3A312C",
"editorCursor.foreground": "#889AFF",
"editorWhitespace.foreground": "#BFBFBF",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let githubDark = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "24292e","token": ""},
{"foreground": "959da5","token": "comment"},
{"foreground": "959da5","token": "punctuation.definition.comment"},
{"foreground": "959da5","token": "string.comment"},
{"foreground": "c8e1ff","token": "constant"},
{"foreground": "c8e1ff","token": "entity.name.constant"},
{"foreground": "c8e1ff","token": "variable.other.constant"},
{"foreground": "c8e1ff","token": "variable.language"},
{"foreground": "b392f0","token": "entity"},
{"foreground": "b392f0","token": "entity.name"},
{"foreground": "f6f8fa","token": "variable.parameter.function"},
{"foreground": "7bcc72","token": "entity.name.tag"},
{"foreground": "ea4a5a","token": "keyword"},
{"foreground": "ea4a5a","token": "storage"},
{"foreground": "ea4a5a","token": "storage.type"},
{"foreground": "f6f8fa","token": "storage.modifier.package"},
{"foreground": "f6f8fa","token": "storage.modifier.import"},
{"foreground": "f6f8fa","token": "storage.type.java"},
{"foreground": "79b8ff","token": "string"},
{"foreground": "79b8ff","token": "punctuation.definition.string"},
{"foreground": "79b8ff","token": "string punctuation.section.embedded source"},
{"foreground": "c8e1ff","token": "support"},
{"foreground": "c8e1ff","token": "meta.property-name"},
{"foreground": "fb8532","token": "variable"},
{"foreground": "f6f8fa","token": "variable.other"},
{"foreground": "d73a49","fontStyle": "bold italic underline","token": "invalid.broken"},
{"foreground": "d73a49","fontStyle": "bold italic underline","token": "invalid.deprecated"},
{"foreground": "fafbfc","background": "d73a49","fontStyle": "italic underline","token": "invalid.illegal"},
{"foreground": "fafbfc","background": "d73a49","fontStyle": "italic underline","token": "carriage-return"},
{"foreground": "d73a49","fontStyle": "bold italic underline","token": "invalid.unimplemented"},
{"foreground": "d73a49","token": "message.error"},
{"foreground": "f6f8fa","token": "string source"},
{"foreground": "c8e1ff","token": "string variable"},
{"foreground": "79b8ff","token": "source.regexp"},
{"foreground": "79b8ff","token": "string.regexp"},
{"foreground": "79b8ff","token": "string.regexp.character-class"},
{"foreground": "79b8ff","token": "string.regexp constant.character.escape"},
{"foreground": "79b8ff","token": "string.regexp source.ruby.embedded"},
{"foreground": "79b8ff","token": "string.regexp string.regexp.arbitrary-repitition"},
{"foreground": "7bcc72","fontStyle": "bold","token": "string.regexp constant.character.escape"},
{"foreground": "c8e1ff","token": "support.constant"},
{"foreground": "c8e1ff","token": "support.variable"},
{"foreground": "c8e1ff","token": "meta.module-reference"},
{"foreground": "fb8532","token": "markup.list"},
{"foreground": "0366d6","fontStyle": "bold","token": "markup.heading"},
{"foreground": "0366d6","fontStyle": "bold","token": "markup.heading entity.name"},
{"foreground": "c8e1ff","token": "markup.quote"},
{"foreground": "f6f8fa","fontStyle": "italic","token": "markup.italic"},
{"foreground": "f6f8fa","fontStyle": "bold","token": "markup.bold"},
{"foreground": "c8e1ff","token": "markup.raw"},
{"foreground": "b31d28","background": "ffeef0","token": "markup.deleted"},
{"foreground": "b31d28","background": "ffeef0","token": "meta.diff.header.from-file"},
{"foreground": "b31d28","background": "ffeef0","token": "punctuation.definition.deleted"},
{"foreground": "176f2c","background": "f0fff4","token": "markup.inserted"},
{"foreground": "176f2c","background": "f0fff4","token": "meta.diff.header.to-file"},
{"foreground": "176f2c","background": "f0fff4","token": "punctuation.definition.inserted"},
{"foreground": "b08800","background": "fffdef","token": "markup.changed"},
{"foreground": "b08800","background": "fffdef","token": "punctuation.definition.changed"},
{"foreground": "2f363d","background": "959da5","token": "markup.ignored"},
{"foreground": "2f363d","background": "959da5","token": "markup.untracked"},
{"foreground": "b392f0","fontStyle": "bold","token": "meta.diff.range"},
{"foreground": "c8e1ff","token": "meta.diff.header"},
{"foreground": "0366d6","fontStyle": "bold","token": "meta.separator"},
{"foreground": "0366d6","token": "meta.output"},
{"foreground": "ffeef0","token": "brackethighlighter.tag"},
{"foreground": "ffeef0","token": "brackethighlighter.curly"},
{"foreground": "ffeef0","token": "brackethighlighter.round"},
{"foreground": "ffeef0","token": "brackethighlighter.square"},
{"foreground": "ffeef0","token": "brackethighlighter.angle"},
{"foreground": "ffeef0","token": "brackethighlighter.quote"},
{"foreground": "d73a49","token": "brackethighlighter.unmatched"},
{"foreground": "d73a49","token": "sublimelinter.mark.error"},
{"foreground": "fb8532","token": "sublimelinter.mark.warning"},
{"foreground": "6a737d","token": "sublimelinter.gutter-mark"},
{"foreground": "79b8ff","fontStyle": "underline","token": "constant.other.reference.link"},
{"foreground": "79b8ff","fontStyle": "underline","token": "string.other.link"}
],
"colors": {
"editor.foreground": "#c9d1d9",
"editor.background": "#0d1117",
"editor.selectionBackground": "#4c2889",
"editor.inactiveSelectionBackground": "#444d56",
"editor.lineHighlightBackground": "#444d56",
"editorCursor.foreground": "#c9d1d9",
"editorWhitespace.foreground": "#6a737d",
"editorIndentGuide.background": "#6a737d",
"editorIndentGuide.activeBackground": "#f6f8fa",
"editor.selectionHighlightBorder": "#444d56",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let githubLight = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "ffffff","token": ""},
{"foreground": "6a737d","token": "comment"},
{"foreground": "6a737d","token": "punctuation.definition.comment"},
{"foreground": "6a737d","token": "string.comment"},
{"foreground": "005cc5","token": "constant"},
{"foreground": "005cc5","token": "entity.name.constant"},
{"foreground": "005cc5","token": "variable.other.constant"},
{"foreground": "005cc5","token": "variable.language"},
{"foreground": "6f42c1","token": "entity"},
{"foreground": "6f42c1","token": "entity.name"},
{"foreground": "24292e","token": "variable.parameter.function"},
{"foreground": "22863a","token": "entity.name.tag"},
{"foreground": "d73a49","token": "keyword"},
{"foreground": "d73a49","token": "storage"},
{"foreground": "d73a49","token": "storage.type"},
{"foreground": "24292e","token": "storage.modifier.package"},
{"foreground": "24292e","token": "storage.modifier.import"},
{"foreground": "24292e","token": "storage.type.java"},
{"foreground": "032f62","token": "string"},
{"foreground": "032f62","token": "punctuation.definition.string"},
{"foreground": "032f62","token": "string punctuation.section.embedded source"},
{"foreground": "005cc5","token": "support"},
{"foreground": "005cc5","token": "meta.property-name"},
{"foreground": "e36209","token": "variable"},
{"foreground": "24292e","token": "variable.other"},
{"foreground": "b31d28","fontStyle": "bold italic underline","token": "invalid.broken"},
{"foreground": "b31d28","fontStyle": "bold italic underline","token": "invalid.deprecated"},
{"foreground": "fafbfc","background": "b31d28","fontStyle": "italic underline","token": "invalid.illegal"},
{"foreground": "fafbfc","background": "d73a49","fontStyle": "italic underline","token": "carriage-return"},
{"foreground": "b31d28","fontStyle": "bold italic underline","token": "invalid.unimplemented"},
{"foreground": "b31d28","token": "message.error"},
{"foreground": "24292e","token": "string source"},
{"foreground": "005cc5","token": "string variable"},
{"foreground": "032f62","token": "source.regexp"},
{"foreground": "032f62","token": "string.regexp"},
{"foreground": "032f62","token": "string.regexp.character-class"},
{"foreground": "032f62","token": "string.regexp constant.character.escape"},
{"foreground": "032f62","token": "string.regexp source.ruby.embedded"},
{"foreground": "032f62","token": "string.regexp string.regexp.arbitrary-repitition"},
{"foreground": "22863a","fontStyle": "bold","token": "string.regexp constant.character.escape"},
{"foreground": "005cc5","token": "support.constant"},
{"foreground": "005cc5","token": "support.variable"},
{"foreground": "005cc5","token": "meta.module-reference"},
{"foreground": "735c0f","token": "markup.list"},
{"foreground": "005cc5","fontStyle": "bold","token": "markup.heading"},
{"foreground": "005cc5","fontStyle": "bold","token": "markup.heading entity.name"},
{"foreground": "22863a","token": "markup.quote"},
{"foreground": "24292e","fontStyle": "italic","token": "markup.italic"},
{"foreground": "24292e","fontStyle": "bold","token": "markup.bold"},
{"foreground": "005cc5","token": "markup.raw"},
{"foreground": "b31d28","background": "ffeef0","token": "markup.deleted"},
{"foreground": "b31d28","background": "ffeef0","token": "meta.diff.header.from-file"},
{"foreground": "b31d28","background": "ffeef0","token": "punctuation.definition.deleted"},
{"foreground": "22863a","background": "f0fff4","token": "markup.inserted"},
{"foreground": "22863a","background": "f0fff4","token": "meta.diff.header.to-file"},
{"foreground": "22863a","background": "f0fff4","token": "punctuation.definition.inserted"},
{"foreground": "e36209","background": "ffebda","token": "markup.changed"},
{"foreground": "e36209","background": "ffebda","token": "punctuation.definition.changed"},
{"foreground": "f6f8fa","background": "005cc5","token": "markup.ignored"},
{"foreground": "f6f8fa","background": "005cc5","token": "markup.untracked"},
{"foreground": "6f42c1","fontStyle": "bold","token": "meta.diff.range"},
{"foreground": "005cc5","token": "meta.diff.header"},
{"foreground": "005cc5","fontStyle": "bold","token": "meta.separator"},
{"foreground": "005cc5","token": "meta.output"},
{"foreground": "586069","token": "brackethighlighter.tag"},
{"foreground": "586069","token": "brackethighlighter.curly"},
{"foreground": "586069","token": "brackethighlighter.round"},
{"foreground": "586069","token": "brackethighlighter.square"},
{"foreground": "586069","token": "brackethighlighter.angle"},
{"foreground": "586069","token": "brackethighlighter.quote"},
{"foreground": "b31d28","token": "brackethighlighter.unmatched"},
{"foreground": "b31d28","token": "sublimelinter.mark.error"},
{"foreground": "e36209","token": "sublimelinter.mark.warning"},
{"foreground": "959da5","token": "sublimelinter.gutter-mark"},
{"foreground": "032f62","fontStyle": "underline","token": "constant.other.reference.link"},
{"foreground": "032f62","fontStyle": "underline","token": "string.other.link"}
],
"colors": {
"editor.foreground": "#24292e",
"editor.background": "#ffffff",
"editor.selectionBackground": "#c8c8fa",
"editor.inactiveSelectionBackground": "#fafbfc",
"editor.lineHighlightBackground": "#fafbfc",
"editorCursor.foreground": "#24292e",
"editorWhitespace.foreground": "#959da5",
"editorIndentGuide.background": "#959da5",
"editorIndentGuide.activeBackground": "#24292e",
"editor.selectionHighlightBorder": "#fafbfc",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let github = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "F8F8FF","token": ""},
{"foreground": "999988","fontStyle": "italic","token": "comment"},
{"foreground": "999999","fontStyle": "bold","token": "comment.block.preprocessor"},
{"foreground": "999999","fontStyle": "bold italic","token": "comment.documentation"},
{"foreground": "999999","fontStyle": "bold italic","token": "comment.block.documentation"},
{"foreground": "a61717","background": "e3d2d2","token": "invalid.illegal"},
{"fontStyle": "bold","token": "keyword"},
{"fontStyle": "bold","token": "storage"},
{"fontStyle": "bold","token": "keyword.operator"},
{"fontStyle": "bold","token": "constant.language"},
{"fontStyle": "bold","token": "support.constant"},
{"foreground": "445588","fontStyle": "bold","token": "storage.type"},
{"foreground": "445588","fontStyle": "bold","token": "support.type"},
{"foreground": "008080","token": "entity.other.attribute-name"},
{"foreground": "0086b3","token": "variable.other"},
{"foreground": "999999","token": "variable.language"},
{"foreground": "445588","fontStyle": "bold","token": "entity.name.type"},
{"foreground": "445588","fontStyle": "bold","token": "entity.other.inherited-class"},
{"foreground": "445588","fontStyle": "bold","token": "support.class"},
{"foreground": "008080","token": "variable.other.constant"},
{"foreground": "800080","token": "constant.character.entity"},
{"foreground": "990000","token": "entity.name.exception"},
{"foreground": "990000","token": "entity.name.function"},
{"foreground": "990000","token": "support.function"},
{"foreground": "990000","token": "keyword.other.name-of-parameter"},
{"foreground": "555555","token": "entity.name.section"},
{"foreground": "000080","token": "entity.name.tag"},
{"foreground": "008080","token": "variable.parameter"},
{"foreground": "008080","token": "support.variable"},
{"foreground": "009999","token": "constant.numeric"},
{"foreground": "009999","token": "constant.other"},
{"foreground": "dd1144","token": "string - string source"},
{"foreground": "dd1144","token": "constant.character"},
{"foreground": "009926","token": "string.regexp"},
{"foreground": "990073","token": "constant.other.symbol"},
{"fontStyle": "bold","token": "punctuation"},
{"foreground": "000000","background": "ffdddd","token": "markup.deleted"},
{"fontStyle": "italic","token": "markup.italic"},
{"foreground": "aa0000","token": "markup.error"},
{"foreground": "999999","token": "markup.heading.1"},
{"foreground": "000000","background": "ddffdd","token": "markup.inserted"},
{"foreground": "888888","token": "markup.output"},
{"foreground": "888888","token": "markup.raw"},
{"foreground": "555555","token": "markup.prompt"},
{"fontStyle": "bold","token": "markup.bold"},
{"foreground": "aaaaaa","token": "markup.heading"},
{"foreground": "aa0000","token": "markup.traceback"},
{"fontStyle": "underline","token": "markup.underline"},
{"foreground": "999999","background": "eaf2f5","token": "meta.diff.range"},
{"foreground": "999999","background": "eaf2f5","token": "meta.diff.index"},
{"foreground": "999999","background": "eaf2f5","token": "meta.separator"},
{"foreground": "999999","background": "ffdddd","token": "meta.diff.header.from-file"},
{"foreground": "999999","background": "ddffdd","token": "meta.diff.header.to-file"},
{"foreground": "4183c4","token": "meta.link"}
],
"colors": {
"editor.foreground": "#000000",
"editor.background": "#F8F8FF",
"editor.selectionBackground": "#B4D5FE",
"editor.lineHighlightBackground": "#FFFEEB",
"editorCursor.foreground": "#666666",
"editorWhitespace.foreground": "#BBBBBB",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let merbivoreSoft = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "161616","token": ""},
{"foreground": "ad2ea4","fontStyle": "italic","token": "comment"},
{"foreground": "fc6f09","token": "keyword"},
{"foreground": "fc6f09","token": "storage"},
{"foreground": "fc83ff","token": "entity.other.inherited-class"},
{"foreground": "58c554","token": "constant.numeric"},
{"foreground": "1edafb","token": "constant"},
{"foreground": "8dff0a","token": "constant.library"},
{"foreground": "fc6f09","token": "support.function"},
{"foreground": "fdc251","token": "constant.language"},
{"foreground": "8dff0a","token": "string"},
{"foreground": "1edafb","token": "support.type"},
{"foreground": "8dff0a","token": "support.constant"},
{"foreground": "fc6f09","token": "meta.tag"},
{"foreground": "fc6f09","token": "declaration.tag"},
{"foreground": "fc6f09","token": "entity.name.tag"},
{"foreground": "ffff89","token": "entity.other.attribute-name"},
{"foreground": "ffffff","background": "990000","token": "invalid"},
{"foreground": "519f50","token": "constant.character.escaped"},
{"foreground": "519f50","token": "constant.character.escape"},
{"foreground": "519f50","token": "string source"},
{"foreground": "519f50","token": "string source.ruby"},
{"foreground": "e6e1dc","background": "144212","token": "markup.inserted"},
{"foreground": "e6e1dc","background": "660000","token": "markup.deleted"},
{"background": "2f33ab","token": "meta.diff.header"},
{"background": "2f33ab","token": "meta.separator.diff"},
{"background": "2f33ab","token": "meta.diff.index"},
{"background": "2f33ab","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#E6E1DC",
"editor.background": "#161616",
"editor.selectionBackground": "#454545",
"editor.lineHighlightBackground": "#333435",
"editorCursor.foreground": "#FFFFFF",
"editorWhitespace.foreground": "#404040",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let monokai = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "272822","token": ""},
{"foreground": "75715e","token": "comment"},
{"foreground": "e6db74","token": "string"},
{"foreground": "ae81ff","token": "constant.numeric"},
{"foreground": "ae81ff","token": "constant.language"},
{"foreground": "ae81ff","token": "constant.character"},
{"foreground": "ae81ff","token": "constant.other"},
{"foreground": "f92672","token": "keyword"},
{"foreground": "f92672","token": "storage"},
{"foreground": "66d9ef","fontStyle": "italic","token": "storage.type"},
{"foreground": "a6e22e","fontStyle": "underline","token": "entity.name.class"},
{"foreground": "a6e22e","fontStyle": "italic underline","token": "entity.other.inherited-class"},
{"foreground": "a6e22e","token": "entity.name.function"},
{"foreground": "fd971f","fontStyle": "italic","token": "variable.parameter"},
{"foreground": "f92672","token": "entity.name.tag"},
{"foreground": "a6e22e","token": "entity.other.attribute-name"},
{"foreground": "66d9ef","token": "support.function"},
{"foreground": "66d9ef","token": "support.constant"},
{"foreground": "66d9ef","fontStyle": "italic","token": "support.type"},
{"foreground": "66d9ef","fontStyle": "italic","token": "support.class"},
{"foreground": "f8f8f0","background": "f92672","token": "invalid"},
{"foreground": "f8f8f0","background": "ae81ff","token": "invalid.deprecated"},
{"foreground": "cfcfc2","token": "meta.structure.dictionary.json string.quoted.double.json"},
{"foreground": "75715e","token": "meta.diff"},
{"foreground": "75715e","token": "meta.diff.header"},
{"foreground": "f92672","token": "markup.deleted"},
{"foreground": "a6e22e","token": "markup.inserted"},
{"foreground": "e6db74","token": "markup.changed"},
{"foreground": "ae81ffa0","token": "constant.numeric.line-number.find-in-files - match"},
{"foreground": "e6db74","token": "entity.name.filename.find-in-files"}
],
"colors": {
"editor.foreground": "#F8F8F2",
"editor.background": "#272822",
"editor.selectionBackground": "#49483E",
"editor.lineHighlightBackground": "#3E3D32",
"editorCursor.foreground": "#F8F8F0",
"editorWhitespace.foreground": "#3B3A32",
"editorIndentGuide.activeBackground": "#9D550FB0",
"editor.selectionHighlightBorder": "#222218",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let nightOwl = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "011627","token": ""},
{"foreground": "637777","token": "comment"},
{"foreground": "addb67","token": "string"},
{"foreground": "ecc48d","token": "vstring.quoted"},
{"foreground": "ecc48d","token": "variable.other.readwrite.js"},
{"foreground": "5ca7e4","token": "string.regexp"},
{"foreground": "5ca7e4","token": "string.regexp keyword.other"},
{"foreground": "5f7e97","token": "meta.function punctuation.separator.comma"},
{"foreground": "f78c6c","token": "constant.numeric"},
{"foreground": "f78c6c","token": "constant.character.numeric"},
{"foreground": "addb67","token": "variable"},
{"foreground": "c792ea","token": "keyword"},
{"foreground": "c792ea","token": "punctuation.accessor"},
{"foreground": "c792ea","token": "storage"},
{"foreground": "c792ea","token": "meta.var.expr"},
{"foreground": "c792ea","token": "meta.class meta.method.declaration meta.var.expr storage.type.jsm"},
{"foreground": "c792ea","token": "storage.type.property.js"},
{"foreground": "c792ea","token": "storage.type.property.ts"},
{"foreground": "c792ea","token": "storage.type.property.tsx"},
{"foreground": "82aaff","token": "storage.type"},
{"foreground": "ffcb8b","token": "entity.name.class"},
{"foreground": "ffcb8b","token": "meta.class entity.name.type.class"},
{"foreground": "addb67","token": "entity.other.inherited-class"},
{"foreground": "82aaff","token": "entity.name.function"},
{"foreground": "addb67","token": "punctuation.definition.variable"},
{"foreground": "d3423e","token": "punctuation.section.embedded"},
{"foreground": "d6deeb","token": "punctuation.terminator.expression"},
{"foreground": "d6deeb","token": "punctuation.definition.arguments"},
{"foreground": "d6deeb","token": "punctuation.definition.array"},
{"foreground": "d6deeb","token": "punctuation.section.array"},
{"foreground": "d6deeb","token": "meta.array"},
{"foreground": "d9f5dd","token": "punctuation.definition.list.begin"},
{"foreground": "d9f5dd","token": "punctuation.definition.list.end"},
{"foreground": "d9f5dd","token": "punctuation.separator.arguments"},
{"foreground": "d9f5dd","token": "punctuation.definition.list"},
{"foreground": "d3423e","token": "string.template meta.template.expression"},
{"foreground": "d6deeb","token": "string.template punctuation.definition.string"},
{"foreground": "c792ea","fontStyle": "italic","token": "italic"},
{"foreground": "addb67","fontStyle": "bold","token": "bold"},
{"foreground": "82aaff","token": "constant.language"},
{"foreground": "82aaff","token": "punctuation.definition.constant"},
{"foreground": "82aaff","token": "variable.other.constant"},
{"foreground": "7fdbca","token": "support.function.construct"},
{"foreground": "7fdbca","token": "keyword.other.new"},
{"foreground": "82aaff","token": "constant.character"},
{"foreground": "82aaff","token": "constant.other"},
{"foreground": "f78c6c","token": "constant.character.escape"},
{"foreground": "addb67","token": "entity.other.inherited-class"},
{"foreground": "d7dbe0","token": "variable.parameter"},
{"foreground": "7fdbca","token": "entity.name.tag"},
{"foreground": "cc2996","token": "punctuation.definition.tag.html"},
{"foreground": "cc2996","token": "punctuation.definition.tag.begin"},
{"foreground": "cc2996","token": "punctuation.definition.tag.end"},
{"foreground": "addb67","token": "entity.other.attribute-name"},
{"foreground": "addb67","token": "entity.name.tag.custom"},
{"foreground": "82aaff","token": "support.function"},
{"foreground": "82aaff","token": "support.constant"},
{"foreground": "7fdbca","token": "upport.constant.meta.property-value"},
{"foreground": "addb67","token": "support.type"},
{"foreground": "addb67","token": "support.class"},
{"foreground": "addb67","token": "support.variable.dom"},
{"foreground": "7fdbca","token": "support.constant"},
{"foreground": "7fdbca","token": "keyword.other.special-method"},
{"foreground": "7fdbca","token": "keyword.other.new"},
{"foreground": "7fdbca","token": "keyword.other.debugger"},
{"foreground": "7fdbca","token": "keyword.control"},
{"foreground": "c792ea","token": "keyword.operator.comparison"},
{"foreground": "c792ea","token": "keyword.control.flow.js"},
{"foreground": "c792ea","token": "keyword.control.flow.ts"},
{"foreground": "c792ea","token": "keyword.control.flow.tsx"},
{"foreground": "c792ea","token": "keyword.control.ruby"},
{"foreground": "c792ea","token": "keyword.control.module.ruby"},
{"foreground": "c792ea","token": "keyword.control.class.ruby"},
{"foreground": "c792ea","token": "keyword.control.def.ruby"},
{"foreground": "c792ea","token": "keyword.control.loop.js"},
{"foreground": "c792ea","token": "keyword.control.loop.ts"},
{"foreground": "c792ea","token": "keyword.control.import.js"},
{"foreground": "c792ea","token": "keyword.control.import.ts"},
{"foreground": "c792ea","token": "keyword.control.import.tsx"},
{"foreground": "c792ea","token": "keyword.control.from.js"},
{"foreground": "c792ea","token": "keyword.control.from.ts"},
{"foreground": "c792ea","token": "keyword.control.from.tsx"},
{"foreground": "ffffff","background": "ff2c83","token": "invalid"},
{"foreground": "ffffff","background": "d3423e","token": "invalid.deprecated"},
{"foreground": "7fdbca","token": "keyword.operator"},
{"foreground": "c792ea","token": "keyword.operator.relational"},
{"foreground": "c792ea","token": "keyword.operator.assignement"},
{"foreground": "c792ea","token": "keyword.operator.arithmetic"},
{"foreground": "c792ea","token": "keyword.operator.bitwise"},
{"foreground": "c792ea","token": "keyword.operator.increment"},
{"foreground": "c792ea","token": "keyword.operator.ternary"},
{"foreground": "637777","token": "comment.line.double-slash"},
{"foreground": "cdebf7","token": "object"},
{"foreground": "ff5874","token": "constant.language.null"},
{"foreground": "d6deeb","token": "meta.brace"},
{"foreground": "c792ea","token": "meta.delimiter.period"},
{"foreground": "d9f5dd","token": "punctuation.definition.string"},
{"foreground": "ff5874","token": "constant.language.boolean"},
{"foreground": "ffffff","token": "object.comma"},
{"foreground": "7fdbca","token": "variable.parameter.function"},
{"foreground": "80cbc4","token": "support.type.vendor.property-name"},
{"foreground": "80cbc4","token": "support.constant.vendor.property-value"},
{"foreground": "80cbc4","token": "support.type.property-name"},
{"foreground": "80cbc4","token": "meta.property-list entity.name.tag"},
{"foreground": "57eaf1","token": "meta.property-list entity.name.tag.reference"},
{"foreground": "f78c6c","token": "constant.other.color.rgb-value punctuation.definition.constant"},
{"foreground": "ffeb95","token": "constant.other.color"},
{"foreground": "ffeb95","token": "keyword.other.unit"},
{"foreground": "c792ea","token": "meta.selector"},
{"foreground": "fad430","token": "entity.other.attribute-name.id"},
{"foreground": "80cbc4","token": "meta.property-name"},
{"foreground": "c792ea","token": "entity.name.tag.doctype"},
{"foreground": "c792ea","token": "meta.tag.sgml.doctype"},
{"foreground": "d9f5dd","token": "punctuation.definition.parameters"},
{"foreground": "ecc48d","token": "string.quoted"},
{"foreground": "ecc48d","token": "string.quoted.double"},
{"foreground": "ecc48d","token": "string.quoted.single"},
{"foreground": "addb67","token": "support.constant.math"},
{"foreground": "addb67","token": "support.type.property-name.json"},
{"foreground": "addb67","token": "support.constant.json"},
{"foreground": "c789d6","token": "meta.structure.dictionary.value.json string.quoted.double"},
{"foreground": "80cbc4","token": "string.quoted.double.json punctuation.definition.string.json"},
{"foreground": "ff5874","token": "meta.structure.dictionary.json meta.structure.dictionary.value constant.language"},
{"foreground": "d6deeb","token": "variable.other.ruby"},
{"foreground": "ecc48d","token": "entity.name.type.class.ruby"},
{"foreground": "ecc48d","token": "keyword.control.class.ruby"},
{"foreground": "ecc48d","token": "meta.class.ruby"},
{"foreground": "7fdbca","token": "constant.language.symbol.hashkey.ruby"},
{"foreground": "e0eddd","background": "a57706","fontStyle": "italic","token": "meta.diff"},
{"foreground": "e0eddd","background": "a57706","fontStyle": "italic","token": "meta.diff.header"},
{"foreground": "ef535090","fontStyle": "italic","token": "markup.deleted"},
{"foreground": "a2bffc","fontStyle": "italic","token": "markup.changed"},
{"foreground": "a2bffc","fontStyle": "italic","token": "meta.diff.header.git"},
{"foreground": "a2bffc","fontStyle": "italic","token": "meta.diff.header.from-file"},
{"foreground": "a2bffc","fontStyle": "italic","token": "meta.diff.header.to-file"},
{"foreground": "219186","background": "eae3ca","token": "markup.inserted"},
{"foreground": "d3201f","token": "other.package.exclude"},
{"foreground": "d3201f","token": "other.remove"},
{"foreground": "269186","token": "other.add"},
{"foreground": "ff5874","token": "constant.language.python"},
{"foreground": "82aaff","token": "variable.parameter.function.python"},
{"foreground": "82aaff","token": "meta.function-call.arguments.python"},
{"foreground": "b2ccd6","token": "meta.function-call.python"},
{"foreground": "b2ccd6","token": "meta.function-call.generic.python"},
{"foreground": "d6deeb","token": "punctuation.python"},
{"foreground": "addb67","token": "entity.name.function.decorator.python"},
{"foreground": "8eace3","token": "source.python variable.language.special"},
{"foreground": "82b1ff","token": "markup.heading.markdown"},
{"foreground": "c792ea","fontStyle": "italic","token": "markup.italic.markdown"},
{"foreground": "addb67","fontStyle": "bold","token": "markup.bold.markdown"},
{"foreground": "697098","token": "markup.quote.markdown"},
{"foreground": "80cbc4","token": "markup.inline.raw.markdown"},
{"foreground": "ff869a","token": "markup.underline.link.markdown"},
{"foreground": "ff869a","token": "markup.underline.link.image.markdown"},
{"foreground": "d6deeb","token": "string.other.link.title.markdown"},
{"foreground": "d6deeb","token": "string.other.link.description.markdown"},
{"foreground": "82b1ff","token": "punctuation.definition.string.markdown"},
{"foreground": "82b1ff","token": "punctuation.definition.string.begin.markdown"},
{"foreground": "82b1ff","token": "punctuation.definition.string.end.markdown"},
{"foreground": "82b1ff","token": "meta.link.inline.markdown punctuation.definition.string"},
{"foreground": "7fdbca","token": "punctuation.definition.metadata.markdown"},
{"foreground": "82b1ff","token": "beginning.punctuation.definition.list.markdown"}
],
"colors": {
"editor.foreground": "#d6deeb",
"editor.background": "#011627",
"editor.selectionBackground": "#5f7e9779",
"editor.lineHighlightBackground": "#010E17",
"editorCursor.foreground": "#80a4c2",
"editorWhitespace.foreground": "#2e2040",
"editorIndentGuide.background": "#5e81ce52",
"editor.selectionHighlightBorder": "#122d42",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let nord = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "2E3440","token": ""},
{"foreground": "616e88","token": "comment"},
{"foreground": "a3be8c","token": "string"},
{"foreground": "b48ead","token": "constant.numeric"},
{"foreground": "81a1c1","token": "constant.language"},
{"foreground": "81a1c1","token": "keyword"},
{"foreground": "81a1c1","token": "storage"},
{"foreground": "81a1c1","token": "storage.type"},
{"foreground": "8fbcbb","token": "entity.name.class"},
{"foreground": "8fbcbb","fontStyle": " bold","token": "entity.other.inherited-class"},
{"foreground": "88c0d0","token": "entity.name.function"},
{"foreground": "81a1c1","token": "entity.name.tag"},
{"foreground": "8fbcbb","token": "entity.other.attribute-name"},
{"foreground": "88c0d0","token": "support.function"},
{"foreground": "f8f8f0","background": "f92672","token": "invalid"},
{"foreground": "f8f8f0","background": "ae81ff","token": "invalid.deprecated"},
{"foreground": "b48ead","token": "constant.color.other.rgb-value"},
{"foreground": "ebcb8b","token": "constant.character.escape"},
{"foreground": "8fbcbb","token": "variable.other.constant"}
],
"colors": {
"editor.foreground": "#D8DEE9",
"editor.background": "#2E3440",
"editor.selectionBackground": "#434C5ECC",
"editor.lineHighlightBackground": "#3B4252",
"editorCursor.foreground": "#D8DEE9",
"editorWhitespace.foreground": "#434C5ECC",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let oceanicNext = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "1B2B34","token": ""},
{"foreground": "65737e","token": "comment"},
{"foreground": "65737e","token": "punctuation.definition.comment"},
{"foreground": "cdd3de","token": "variable"},
{"foreground": "c594c5","token": "keyword"},
{"foreground": "c594c5","token": "storage.type"},
{"foreground": "c594c5","token": "storage.modifier"},
{"foreground": "5fb3b3","token": "keyword.operator"},
{"foreground": "5fb3b3","token": "constant.other.color"},
{"foreground": "5fb3b3","token": "punctuation"},
{"foreground": "5fb3b3","token": "meta.tag"},
{"foreground": "5fb3b3","token": "punctuation.definition.tag"},
{"foreground": "5fb3b3","token": "punctuation.separator.inheritance.php"},
{"foreground": "5fb3b3","token": "punctuation.definition.tag.html"},
{"foreground": "5fb3b3","token": "punctuation.definition.tag.begin.html"},
{"foreground": "5fb3b3","token": "punctuation.definition.tag.end.html"},
{"foreground": "5fb3b3","token": "punctuation.section.embedded"},
{"foreground": "5fb3b3","token": "keyword.other.template"},
{"foreground": "5fb3b3","token": "keyword.other.substitution"},
{"foreground": "eb606b","token": "entity.name.tag"},
{"foreground": "eb606b","token": "meta.tag.sgml"},
{"foreground": "eb606b","token": "markup.deleted.git_gutter"},
{"foreground": "6699cc","token": "entity.name.function"},
{"foreground": "6699cc","token": "meta.function-call"},
{"foreground": "6699cc","token": "variable.function"},
{"foreground": "6699cc","token": "support.function"},
{"foreground": "6699cc","token": "keyword.other.special-method"},
{"foreground": "6699cc","token": "meta.block-level"},
{"foreground": "f2777a","token": "support.other.variable"},
{"foreground": "f2777a","token": "string.other.link"},
{"foreground": "f99157","token": "constant.numeric"},
{"foreground": "f99157","token": "constant.language"},
{"foreground": "f99157","token": "support.constant"},
{"foreground": "f99157","token": "constant.character"},
{"foreground": "f99157","token": "variable.parameter"},
{"foreground": "f99157","token": "keyword.other.unit"},
{"foreground": "99c794","fontStyle": "normal","token": "string"},
{"foreground": "99c794","fontStyle": "normal","token": "constant.other.symbol"},
{"foreground": "99c794","fontStyle": "normal","token": "constant.other.key"},
{"foreground": "99c794","fontStyle": "normal","token": "entity.other.inherited-class"},
{"foreground": "99c794","fontStyle": "normal","token": "markup.heading"},
{"foreground": "99c794","fontStyle": "normal","token": "markup.inserted.git_gutter"},
{"foreground": "99c794","fontStyle": "normal","token": "meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js"},
{"foreground": "fac863","token": "entity.name.class"},
{"foreground": "fac863","token": "entity.name.type.class"},
{"foreground": "fac863","token": "support.type"},
{"foreground": "fac863","token": "support.class"},
{"foreground": "fac863","token": "support.orther.namespace.use.php"},
{"foreground": "fac863","token": "meta.use.php"},
{"foreground": "fac863","token": "support.other.namespace.php"},
{"foreground": "fac863","token": "markup.changed.git_gutter"},
{"foreground": "ec5f67","token": "entity.name.module.js"},
{"foreground": "ec5f67","token": "variable.import.parameter.js"},
{"foreground": "ec5f67","token": "variable.other.class.js"},
{"foreground": "ec5f67","fontStyle": "italic","token": "variable.language"},
{"foreground": "cdd3de","token": "meta.group.braces.curly.js constant.other.object.key.js string.unquoted.label.js"},
{"foreground": "d8dee9","token": "meta.class-method.js entity.name.function.js"},
{"foreground": "d8dee9","token": "variable.function.constructor"},
{"foreground": "d8dee9","token": "meta.class.js meta.class.property.js meta.method.js string.unquoted.js entity.name.function.js"},
{"foreground": "bb80b3","token": "entity.other.attribute-name"},
{"foreground": "99c794","token": "markup.inserted"},
{"foreground": "ec5f67","token": "markup.deleted"},
{"foreground": "bb80b3","token": "markup.changed"},
{"foreground": "5fb3b3","token": "string.regexp"},
{"foreground": "5fb3b3","token": "constant.character.escape"},
{"fontStyle": "underline","token": "*url*"},
{"fontStyle": "underline","token": "*link*"},
{"fontStyle": "underline","token": "*uri*"},
{"foreground": "ab7967","token": "constant.numeric.line-number.find-in-files - match"},
{"foreground": "99c794","token": "entity.name.filename.find-in-files"},
{"foreground": "6699cc","fontStyle": "italic","token": "tag.decorator.js entity.name.tag.js"},
{"foreground": "6699cc","fontStyle": "italic","token": "tag.decorator.js punctuation.definition.tag.js"},
{"foreground": "ec5f67","fontStyle": "italic","token": "source.js constant.other.object.key.js string.unquoted.label.js"},
{"foreground": "fac863","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "fac863","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "c594c5","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "c594c5","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "d8dee9","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "d8dee9","token": "source.json meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "6699cc","token": "source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "6699cc","token": "source.json meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "ab7967","token": "source.json meta meta meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "ab7967","token": "source.json meta meta meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "ec5f67","token": "source.json meta meta meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "ec5f67","token": "source.json meta meta meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "f99157","token": "source.json meta meta meta meta.structure.dictionary.json string.quoted.double.json - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "f99157","token": "source.json meta meta meta meta.structure.dictionary.json punctuation.definition.string - meta meta meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "fac863","token": "source.json meta meta.structure.dictionary.json string.quoted.double.json - meta meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "fac863","token": "source.json meta meta.structure.dictionary.json punctuation.definition.string - meta meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"},
{"foreground": "c594c5","token": "source.json meta.structure.dictionary.json string.quoted.double.json - meta.structure.dictionary.json meta.structure.dictionary.value.json string.quoted.double.json"},
{"foreground": "c594c5","token": "source.json meta.structure.dictionary.json punctuation.definition.string - meta.structure.dictionary.json meta.structure.dictionary.value.json punctuation.definition.string"}
],
"colors": {
"editor.foreground": "#CDD3DE",
"editor.background": "#1B2B34",
"editor.selectionBackground": "#4f5b66",
"editor.lineHighlightBackground": "#65737e55",
"editorCursor.foreground": "#c0c5ce",
"editorWhitespace.foreground": "#65737e",
"editorIndentGuide.background": "#65737F",
"editorIndentGuide.activeBackground": "#FBC95A",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let pastelsOnDark = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "211E1E","token": ""},
{"foreground": "555555","token": "comment"},
{"foreground": "555555","token": "comment.block"},
{"foreground": "ad9361","token": "string"},
{"foreground": "cccccc","token": "constant.numeric"},
{"foreground": "a1a1ff","token": "keyword"},
{"foreground": "2f006e","token": "meta.preprocessor"},
{"fontStyle": "bold","token": "keyword.control.import"},
{"foreground": "a1a1ff","token": "support.function"},
{"foreground": "0000ff","token": "declaration.function function-result"},
{"fontStyle": "bold","token": "declaration.function function-name"},
{"fontStyle": "bold","token": "declaration.function argument-name"},
{"foreground": "0000ff","token": "declaration.function function-arg-type"},
{"fontStyle": "italic","token": "declaration.function function-argument"},
{"fontStyle": "underline","token": "declaration.class class-name"},
{"fontStyle": "italic underline","token": "declaration.class class-inheritance"},
{"foreground": "fff9f9","background": "ff0000","fontStyle": "bold","token": "invalid"},
{"background": "ffd0d0","token": "invalid.deprecated.trailing-whitespace"},
{"fontStyle": "italic","token": "declaration.section section-name"},
{"foreground": "c10006","token": "string.interpolation"},
{"foreground": "666666","token": "string.regexp"},
{"foreground": "c1c144","token": "variable"},
{"foreground": "6782d3","token": "constant"},
{"foreground": "afa472","token": "constant.character"},
{"foreground": "de8e30","fontStyle": "bold","token": "constant.language"},
{"fontStyle": "underline","token": "embedded"},
{"foreground": "858ef4","token": "keyword.markup.element-name"},
{"foreground": "9b456f","token": "keyword.markup.attribute-name"},
{"foreground": "9b456f","token": "meta.attribute-with-value"},
{"foreground": "c82255","fontStyle": "bold","token": "keyword.exception"},
{"foreground": "47b8d6","token": "keyword.operator"},
{"foreground": "6969fa","fontStyle": "bold","token": "keyword.control"},
{"foreground": "68685b","token": "meta.tag.preprocessor.xml"},
{"foreground": "888888","token": "meta.tag.sgml.doctype"},
{"fontStyle": "italic","token": "string.quoted.docinfo.doctype.DTD"},
{"foreground": "909090","token": "comment.other.server-side-include.xhtml"},
{"foreground": "909090","token": "comment.other.server-side-include.html"},
{"foreground": "858ef4","token": "text.html declaration.tag"},
{"foreground": "858ef4","token": "text.html meta.tag"},
{"foreground": "858ef4","token": "text.html entity.name.tag.xhtml"},
{"foreground": "9b456f","token": "keyword.markup.attribute-name"},
{"foreground": "777777","token": "keyword.other.phpdoc.php"},
{"foreground": "c82255","token": "keyword.other.include.php"},
{"foreground": "de8e20","fontStyle": "bold","token": "support.constant.core.php"},
{"foreground": "de8e10","fontStyle": "bold","token": "support.constant.std.php"},
{"foreground": "b72e1d","token": "variable.other.global.php"},
{"foreground": "00ff00","token": "variable.other.global.safer.php"},
{"foreground": "bfa36d","token": "string.quoted.single.php"},
{"foreground": "6969fa","token": "keyword.storage.php"},
{"foreground": "ad9361","token": "string.quoted.double.php"},
{"foreground": "ec9e00","token": "entity.other.attribute-name.id.css"},
{"foreground": "b8cd06","fontStyle": "bold","token": "entity.name.tag.css"},
{"foreground": "edca06","token": "entity.other.attribute-name.class.css"},
{"foreground": "2e759c","token": "entity.other.attribute-name.pseudo-class.css"},
{"foreground": "ffffff","background": "ff0000","token": "invalid.bad-comma.css"},
{"foreground": "9b2e4d","token": "support.constant.property-value.css"},
{"foreground": "e1c96b","token": "support.type.property-name.css"},
{"foreground": "666633","token": "constant.other.rgb-value.css"},
{"foreground": "666633","token": "support.constant.font-name.css"},
{"foreground": "7171f3","token": "support.constant.tm-language-def"},
{"foreground": "7171f3","token": "support.constant.name.tm-language-def"},
{"foreground": "6969fa","token": "keyword.other.unit.css"}
],
"colors": {
"editor.foreground": "#DADADA",
"editor.background": "#211E1E",
"editor.selectionBackground": "#73597E80",
"editor.lineHighlightBackground": "#353030",
"editorCursor.foreground": "#FFFFFF",
"editorWhitespace.foreground": "#4F4D4D",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let sunburst = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "000000","token": ""},
{"foreground": "aeaeae","fontStyle": "italic","token": "comment"},
{"foreground": "3387cc","token": "constant"},
{"foreground": "89bdff","token": "entity"},
{"foreground": "e28964","token": "keyword"},
{"foreground": "99cf50","token": "storage"},
{"foreground": "65b042","token": "string"},
{"foreground": "9b859d","token": "support"},
{"foreground": "3e87e3","token": "variable"},
{"foreground": "fd5ff1","fontStyle": "italic underline","token": "invalid.deprecated"},
{"foreground": "fd5ff1","background": "562d56bf","token": "invalid.illegal"},
{"background": "b1b3ba08","token": "text source"},
{"foreground": "9b5c2e","fontStyle": "italic","token": "entity.other.inherited-class"},
{"foreground": "daefa3","token": "string.quoted source"},
{"foreground": "ddf2a4","token": "string constant"},
{"foreground": "e9c062","token": "string.regexp"},
{"foreground": "cf7d34","token": "string.regexp constant.character.escape"},
{"foreground": "cf7d34","token": "string.regexp source.ruby.embedded"},
{"foreground": "cf7d34","token": "string.regexp string.regexp.arbitrary-repitition"},
{"foreground": "8a9a95","token": "string variable"},
{"foreground": "dad085","token": "support.function"},
{"foreground": "cf6a4c","token": "support.constant"},
{"foreground": "8996a8","token": "meta.preprocessor.c"},
{"foreground": "afc4db","token": "meta.preprocessor.c keyword"},
{"fontStyle": "underline","token": "entity.name.type"},
{"foreground": "676767","fontStyle": "italic","token": "meta.cast"},
{"foreground": "494949","token": "meta.sgml.html meta.doctype"},
{"foreground": "494949","token": "meta.sgml.html meta.doctype entity"},
{"foreground": "494949","token": "meta.sgml.html meta.doctype string"},
{"foreground": "494949","token": "meta.xml-processing"},
{"foreground": "494949","token": "meta.xml-processing entity"},
{"foreground": "494949","token": "meta.xml-processing string"},
{"foreground": "89bdff","token": "meta.tag"},
{"foreground": "89bdff","token": "meta.tag entity"},
{"foreground": "e0c589","token": "source entity.name.tag"},
{"foreground": "e0c589","token": "source entity.other.attribute-name"},
{"foreground": "e0c589","token": "meta.tag.inline"},
{"foreground": "e0c589","token": "meta.tag.inline entity"},
{"foreground": "e18964","token": "entity.name.tag.namespace"},
{"foreground": "e18964","token": "entity.other.attribute-name.namespace"},
{"foreground": "cda869","token": "meta.selector.css entity.name.tag"},
{"foreground": "8f9d6a","token": "meta.selector.css entity.other.attribute-name.tag.pseudo-class"},
{"foreground": "8b98ab","token": "meta.selector.css entity.other.attribute-name.id"},
{"foreground": "9b703f","token": "meta.selector.css entity.other.attribute-name.class"},
{"foreground": "c5af75","token": "support.type.property-name.css"},
{"foreground": "f9ee98","token": "meta.property-group support.constant.property-value.css"},
{"foreground": "f9ee98","token": "meta.property-value support.constant.property-value.css"},
{"foreground": "8693a5","token": "meta.preprocessor.at-rule keyword.control.at-rule"},
{"foreground": "dd7b3b","token": "meta.property-value support.constant.named-color.css"},
{"foreground": "dd7b3b","token": "meta.property-value constant"},
{"foreground": "8f9d6a","token": "meta.constructor.argument.css"},
{"foreground": "f8f8f8","background": "0e2231","fontStyle": "italic","token": "meta.diff"},
{"foreground": "f8f8f8","background": "0e2231","fontStyle": "italic","token": "meta.diff.header"},
{"foreground": "f8f8f8","background": "420e09","token": "markup.deleted"},
{"foreground": "f8f8f8","background": "4a410d","token": "markup.changed"},
{"foreground": "f8f8f8","background": "253b22","token": "markup.inserted"},
{"foreground": "e9c062","fontStyle": "italic","token": "markup.italic"},
{"foreground": "e9c062","fontStyle": "bold","token": "markup.bold"},
{"foreground": "e18964","fontStyle": "underline","token": "markup.underline"},
{"foreground": "e1d4b9","background": "fee09c12","fontStyle": "italic","token": "markup.quote"},
{"foreground": "fedcc5","background": "632d04","token": "markup.heading"},
{"foreground": "fedcc5","background": "632d04","token": "markup.heading entity"},
{"foreground": "e1d4b9","token": "markup.list"},
{"foreground": "578bb3","background": "b1b3ba08","token": "markup.raw"},
{"foreground": "f67b37","fontStyle": "italic","token": "markup comment"},
{"foreground": "60a633","background": "242424","token": "meta.separator"},
{"background": "eeeeee29","token": "meta.line.entry.logfile"},
{"background": "eeeeee29","token": "meta.line.exit.logfile"},
{"background": "751012","token": "meta.line.error.logfile"}
],
"colors": {
"editor.foreground": "#F8F8F8",
"editor.background": "#000000",
"editor.selectionBackground": "#DDF0FF33",
"editor.lineHighlightBackground": "#FFFFFF0D",
"editorCursor.foreground": "#A7A7A7",
"editorWhitespace.foreground": "#CAE2FB3D",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let tomorrowNightBlue = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "002451","token": ""},
{"foreground": "7285b7","token": "comment"},
{"foreground": "ffffff","token": "keyword.operator.class"},
{"foreground": "ffffff","token": "keyword.operator"},
{"foreground": "ffffff","token": "constant.other"},
{"foreground": "ffffff","token": "source.php.embedded.line"},
{"foreground": "ff9da4","token": "variable"},
{"foreground": "ff9da4","token": "support.other.variable"},
{"foreground": "ff9da4","token": "string.other.link"},
{"foreground": "ff9da4","token": "string.regexp"},
{"foreground": "ff9da4","token": "entity.name.tag"},
{"foreground": "ff9da4","token": "entity.other.attribute-name"},
{"foreground": "ff9da4","token": "meta.tag"},
{"foreground": "ff9da4","token": "declaration.tag"},
{"foreground": "ff9da4","token": "markup.deleted.git_gutter"},
{"foreground": "ffc58f","token": "constant.numeric"},
{"foreground": "ffc58f","token": "constant.language"},
{"foreground": "ffc58f","token": "support.constant"},
{"foreground": "ffc58f","token": "constant.character"},
{"foreground": "ffc58f","token": "variable.parameter"},
{"foreground": "ffc58f","token": "punctuation.section.embedded"},
{"foreground": "ffc58f","token": "keyword.other.unit"},
{"foreground": "ffeead","token": "entity.name.class"},
{"foreground": "ffeead","token": "entity.name.type.class"},
{"foreground": "ffeead","token": "support.type"},
{"foreground": "ffeead","token": "support.class"},
{"foreground": "d1f1a9","token": "string"},
{"foreground": "d1f1a9","token": "constant.other.symbol"},
{"foreground": "d1f1a9","token": "entity.other.inherited-class"},
{"foreground": "d1f1a9","token": "markup.heading"},
{"foreground": "d1f1a9","token": "markup.inserted.git_gutter"},
{"foreground": "99ffff","token": "keyword.operator"},
{"foreground": "99ffff","token": "constant.other.color"},
{"foreground": "bbdaff","token": "entity.name.function"},
{"foreground": "bbdaff","token": "meta.function-call"},
{"foreground": "bbdaff","token": "support.function"},
{"foreground": "bbdaff","token": "keyword.other.special-method"},
{"foreground": "bbdaff","token": "meta.block-level"},
{"foreground": "bbdaff","token": "markup.changed.git_gutter"},
{"foreground": "ebbbff","token": "keyword"},
{"foreground": "ebbbff","token": "storage"},
{"foreground": "ebbbff","token": "storage.type"},
{"foreground": "ebbbff","token": "entity.name.tag.css"},
{"foreground": "ffffff","background": "f99da5","token": "invalid"},
{"foreground": "ffffff","background": "bbdafe","token": "meta.separator"},
{"foreground": "ffffff","background": "ebbbff","token": "invalid.deprecated"},
{"foreground": "ffffff","token": "markup.inserted.diff"},
{"foreground": "ffffff","token": "markup.deleted.diff"},
{"foreground": "ffffff","token": "meta.diff.header.to-file"},
{"foreground": "ffffff","token": "meta.diff.header.from-file"},
{"foreground": "718c00","token": "markup.inserted.diff"},
{"foreground": "718c00","token": "meta.diff.header.to-file"},
{"foreground": "c82829","token": "markup.deleted.diff"},
{"foreground": "c82829","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.to-file"},
{"foreground": "3e999f","fontStyle": "italic","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#FFFFFF",
"editor.background": "#002451",
"editor.selectionBackground": "#003F8E",
"editor.lineHighlightBackground": "#00346E",
"editorCursor.foreground": "#FFFFFF",
"editorWhitespace.foreground": "#404F7D",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let tomorrowNightBright = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "000000","token": ""},
{"foreground": "969896","token": "comment"},
{"foreground": "eeeeee","token": "keyword.operator.class"},
{"foreground": "eeeeee","token": "constant.other"},
{"foreground": "eeeeee","token": "source.php.embedded.line"},
{"foreground": "d54e53","token": "variable"},
{"foreground": "d54e53","token": "support.other.variable"},
{"foreground": "d54e53","token": "string.other.link"},
{"foreground": "d54e53","token": "string.regexp"},
{"foreground": "d54e53","token": "entity.name.tag"},
{"foreground": "d54e53","token": "entity.other.attribute-name"},
{"foreground": "d54e53","token": "meta.tag"},
{"foreground": "d54e53","token": "declaration.tag"},
{"foreground": "d54e53","token": "markup.deleted.git_gutter"},
{"foreground": "e78c45","token": "constant.numeric"},
{"foreground": "e78c45","token": "constant.language"},
{"foreground": "e78c45","token": "support.constant"},
{"foreground": "e78c45","token": "constant.character"},
{"foreground": "e78c45","token": "variable.parameter"},
{"foreground": "e78c45","token": "punctuation.section.embedded"},
{"foreground": "e78c45","token": "keyword.other.unit"},
{"foreground": "e7c547","token": "entity.name.class"},
{"foreground": "e7c547","token": "entity.name.type.class"},
{"foreground": "e7c547","token": "support.type"},
{"foreground": "e7c547","token": "support.class"},
{"foreground": "b9ca4a","token": "string"},
{"foreground": "b9ca4a","token": "constant.other.symbol"},
{"foreground": "b9ca4a","token": "entity.other.inherited-class"},
{"foreground": "b9ca4a","token": "markup.heading"},
{"foreground": "b9ca4a","token": "markup.inserted.git_gutter"},
{"foreground": "70c0b1","token": "keyword.operator"},
{"foreground": "70c0b1","token": "constant.other.color"},
{"foreground": "7aa6da","token": "entity.name.function"},
{"foreground": "7aa6da","token": "meta.function-call"},
{"foreground": "7aa6da","token": "support.function"},
{"foreground": "7aa6da","token": "keyword.other.special-method"},
{"foreground": "7aa6da","token": "meta.block-level"},
{"foreground": "7aa6da","token": "markup.changed.git_gutter"},
{"foreground": "c397d8","token": "keyword"},
{"foreground": "c397d8","token": "storage"},
{"foreground": "c397d8","token": "storage.type"},
{"foreground": "c397d8","token": "entity.name.tag.css"},
{"foreground": "ced2cf","background": "df5f5f","token": "invalid"},
{"foreground": "ced2cf","background": "82a3bf","token": "meta.separator"},
{"foreground": "ced2cf","background": "b798bf","token": "invalid.deprecated"},
{"foreground": "ffffff","token": "markup.inserted.diff"},
{"foreground": "ffffff","token": "markup.deleted.diff"},
{"foreground": "ffffff","token": "meta.diff.header.to-file"},
{"foreground": "ffffff","token": "meta.diff.header.from-file"},
{"foreground": "718c00","token": "markup.inserted.diff"},
{"foreground": "718c00","token": "meta.diff.header.to-file"},
{"foreground": "c82829","token": "markup.deleted.diff"},
{"foreground": "c82829","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.to-file"},
{"foreground": "3e999f","fontStyle": "italic","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#DEDEDE",
"editor.background": "#000000",
"editor.selectionBackground": "#424242",
"editor.lineHighlightBackground": "#2A2A2A",
"editorCursor.foreground": "#9F9F9F",
"editorWhitespace.foreground": "#343434",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let tomorrowNightEighties = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "2D2D2D","token": ""},
{"foreground": "999999","token": "comment"},
{"foreground": "cccccc","token": "keyword.operator.class"},
{"foreground": "cccccc","token": "constant.other"},
{"foreground": "cccccc","token": "source.php.embedded.line"},
{"foreground": "f2777a","token": "variable"},
{"foreground": "f2777a","token": "support.other.variable"},
{"foreground": "f2777a","token": "string.other.link"},
{"foreground": "f2777a","token": "entity.name.tag"},
{"foreground": "f2777a","token": "entity.other.attribute-name"},
{"foreground": "f2777a","token": "meta.tag"},
{"foreground": "f2777a","token": "declaration.tag"},
{"foreground": "f2777a","token": "markup.deleted.git_gutter"},
{"foreground": "f99157","token": "constant.numeric"},
{"foreground": "f99157","token": "constant.language"},
{"foreground": "f99157","token": "support.constant"},
{"foreground": "f99157","token": "constant.character"},
{"foreground": "f99157","token": "variable.parameter"},
{"foreground": "f99157","token": "punctuation.section.embedded"},
{"foreground": "f99157","token": "keyword.other.unit"},
{"foreground": "ffcc66","token": "entity.name.class"},
{"foreground": "ffcc66","token": "entity.name.type.class"},
{"foreground": "ffcc66","token": "support.type"},
{"foreground": "ffcc66","token": "support.class"},
{"foreground": "99cc99","token": "string"},
{"foreground": "99cc99","token": "constant.other.symbol"},
{"foreground": "99cc99","token": "entity.other.inherited-class"},
{"foreground": "99cc99","token": "markup.heading"},
{"foreground": "99cc99","token": "markup.inserted.git_gutter"},
{"foreground": "66cccc","token": "keyword.operator"},
{"foreground": "66cccc","token": "constant.other.color"},
{"foreground": "6699cc","token": "entity.name.function"},
{"foreground": "6699cc","token": "meta.function-call"},
{"foreground": "6699cc","token": "support.function"},
{"foreground": "6699cc","token": "keyword.other.special-method"},
{"foreground": "6699cc","token": "meta.block-level"},
{"foreground": "6699cc","token": "markup.changed.git_gutter"},
{"foreground": "cc99cc","token": "keyword"},
{"foreground": "cc99cc","token": "storage"},
{"foreground": "cc99cc","token": "storage.type"},
{"foreground": "cc99cc","token": "entity.name.tag.css"},
{"foreground": "cdcdcd","background": "f2777a","token": "invalid"},
{"foreground": "cdcdcd","background": "99cccc","token": "meta.separator"},
{"foreground": "cdcdcd","background": "cc99cc","token": "invalid.deprecated"},
{"foreground": "ffffff","token": "markup.inserted.diff"},
{"foreground": "ffffff","token": "markup.deleted.diff"},
{"foreground": "ffffff","token": "meta.diff.header.to-file"},
{"foreground": "ffffff","token": "meta.diff.header.from-file"},
{"foreground": "718c00","token": "markup.inserted.diff"},
{"foreground": "718c00","token": "meta.diff.header.to-file"},
{"foreground": "c82829","token": "markup.deleted.diff"},
{"foreground": "c82829","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.to-file"},
{"foreground": "3e999f","fontStyle": "italic","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#CCCCCC",
"editor.background": "#2D2D2D",
"editor.selectionBackground": "#515151",
"editor.lineHighlightBackground": "#393939",
"editorCursor.foreground": "#CCCCCC",
"editorWhitespace.foreground": "#6A6A6A",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let tomorrowNight = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "1D1F21","token": ""},
{"foreground": "969896","token": "comment"},
{"foreground": "ced1cf","token": "keyword.operator.class"},
{"foreground": "ced1cf","token": "constant.other"},
{"foreground": "ced1cf","token": "source.php.embedded.line"},
{"foreground": "cc6666","token": "variable"},
{"foreground": "cc6666","token": "support.other.variable"},
{"foreground": "cc6666","token": "string.other.link"},
{"foreground": "cc6666","token": "string.regexp"},
{"foreground": "cc6666","token": "entity.name.tag"},
{"foreground": "cc6666","token": "entity.other.attribute-name"},
{"foreground": "cc6666","token": "meta.tag"},
{"foreground": "cc6666","token": "declaration.tag"},
{"foreground": "cc6666","token": "markup.deleted.git_gutter"},
{"foreground": "de935f","token": "constant.numeric"},
{"foreground": "de935f","token": "constant.language"},
{"foreground": "de935f","token": "support.constant"},
{"foreground": "de935f","token": "constant.character"},
{"foreground": "de935f","token": "variable.parameter"},
{"foreground": "de935f","token": "punctuation.section.embedded"},
{"foreground": "de935f","token": "keyword.other.unit"},
{"foreground": "f0c674","token": "entity.name.class"},
{"foreground": "f0c674","token": "entity.name.type.class"},
{"foreground": "f0c674","token": "support.type"},
{"foreground": "f0c674","token": "support.class"},
{"foreground": "b5bd68","token": "string"},
{"foreground": "b5bd68","token": "constant.other.symbol"},
{"foreground": "b5bd68","token": "entity.other.inherited-class"},
{"foreground": "b5bd68","token": "markup.heading"},
{"foreground": "b5bd68","token": "markup.inserted.git_gutter"},
{"foreground": "8abeb7","token": "keyword.operator"},
{"foreground": "8abeb7","token": "constant.other.color"},
{"foreground": "81a2be","token": "entity.name.function"},
{"foreground": "81a2be","token": "meta.function-call"},
{"foreground": "81a2be","token": "support.function"},
{"foreground": "81a2be","token": "keyword.other.special-method"},
{"foreground": "81a2be","token": "meta.block-level"},
{"foreground": "81a2be","token": "markup.changed.git_gutter"},
{"foreground": "b294bb","token": "keyword"},
{"foreground": "b294bb","token": "storage"},
{"foreground": "b294bb","token": "storage.type"},
{"foreground": "b294bb","token": "entity.name.tag.css"},
{"foreground": "ced2cf","background": "df5f5f","token": "invalid"},
{"foreground": "ced2cf","background": "82a3bf","token": "meta.separator"},
{"foreground": "ced2cf","background": "b798bf","token": "invalid.deprecated"},
{"foreground": "ffffff","token": "markup.inserted.diff"},
{"foreground": "ffffff","token": "markup.deleted.diff"},
{"foreground": "ffffff","token": "meta.diff.header.to-file"},
{"foreground": "ffffff","token": "meta.diff.header.from-file"},
{"foreground": "718c00","token": "markup.inserted.diff"},
{"foreground": "718c00","token": "meta.diff.header.to-file"},
{"foreground": "c82829","token": "markup.deleted.diff"},
{"foreground": "c82829","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.to-file"},
{"foreground": "3e999f","fontStyle": "italic","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#C5C8C6",
"editor.background": "#1D1F21",
"editor.selectionBackground": "#3074B8",
"editor.lineHighlightBackground": "#282A2E",
"editorCursor.foreground": "#AEAFAD",
"editorWhitespace.foreground": "#4B4E55",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let tomorrow = {
"base": "vs",
"inherit": true,
"rules": [
{"background": "FFFFFF","token": ""},
{"foreground": "8e908c","token": "comment"},
{"foreground": "666969","token": "keyword.operator.class"},
{"foreground": "666969","token": "constant.other"},
{"foreground": "666969","token": "source.php.embedded.line"},
{"foreground": "c82829","token": "variable"},
{"foreground": "c82829","token": "support.other.variable"},
{"foreground": "c82829","token": "string.other.link"},
{"foreground": "c82829","token": "string.regexp"},
{"foreground": "c82829","token": "entity.name.tag"},
{"foreground": "c82829","token": "entity.other.attribute-name"},
{"foreground": "c82829","token": "meta.tag"},
{"foreground": "c82829","token": "declaration.tag"},
{"foreground": "c82829","token": "markup.deleted.git_gutter"},
{"foreground": "f5871f","token": "constant.numeric"},
{"foreground": "f5871f","token": "constant.language"},
{"foreground": "f5871f","token": "support.constant"},
{"foreground": "f5871f","token": "constant.character"},
{"foreground": "f5871f","token": "variable.parameter"},
{"foreground": "f5871f","token": "punctuation.section.embedded"},
{"foreground": "f5871f","token": "keyword.other.unit"},
{"foreground": "c99e00","token": "entity.name.class"},
{"foreground": "c99e00","token": "entity.name.type.class"},
{"foreground": "c99e00","token": "support.type"},
{"foreground": "c99e00","token": "support.class"},
{"foreground": "718c00","token": "string"},
{"foreground": "718c00","token": "constant.other.symbol"},
{"foreground": "718c00","token": "entity.other.inherited-class"},
{"foreground": "718c00","token": "markup.heading"},
{"foreground": "718c00","token": "markup.inserted.git_gutter"},
{"foreground": "3e999f","token": "keyword.operator"},
{"foreground": "3e999f","token": "constant.other.color"},
{"foreground": "4271ae","token": "entity.name.function"},
{"foreground": "4271ae","token": "meta.function-call"},
{"foreground": "4271ae","token": "support.function"},
{"foreground": "4271ae","token": "keyword.other.special-method"},
{"foreground": "4271ae","token": "meta.block-level"},
{"foreground": "4271ae","token": "markup.changed.git_gutter"},
{"foreground": "8959a8","token": "keyword"},
{"foreground": "8959a8","token": "storage"},
{"foreground": "8959a8","token": "storage.type"},
{"foreground": "ffffff","background": "c82829","token": "invalid"},
{"foreground": "ffffff","background": "4271ae","token": "meta.separator"},
{"foreground": "ffffff","background": "8959a8","token": "invalid.deprecated"},
{"foreground": "ffffff","token": "markup.inserted.diff"},
{"foreground": "ffffff","token": "markup.deleted.diff"},
{"foreground": "ffffff","token": "meta.diff.header.to-file"},
{"foreground": "ffffff","token": "meta.diff.header.from-file"},
{"background": "718c00","token": "markup.inserted.diff"},
{"background": "718c00","token": "meta.diff.header.to-file"},
{"background": "c82829","token": "markup.deleted.diff"},
{"background": "c82829","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.from-file"},
{"foreground": "ffffff","background": "4271ae","token": "meta.diff.header.to-file"},
{"foreground": "3e999f","fontStyle": "italic","token": "meta.diff.range"}
],
"colors": {
"editor.foreground": "#4D4D4C",
"editor.background": "#FFFFFF",
"editor.selectionBackground": "#D6D6D6",
"editor.lineHighlightBackground": "#EFEFEF",
"editorCursor.foreground": "#AEAFAD",
"editorWhitespace.foreground": "#D1D1D1",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
let twilight = {
"base": "vs-dark",
"inherit": true,
"rules": [
{"background": "141414","token": ""},
{"foreground": "5f5a60","fontStyle": "italic","token": "comment"},
{"foreground": "cf6a4c","token": "constant"},
{"foreground": "9b703f","token": "entity"},
{"foreground": "cda869","token": "keyword"},
{"foreground": "f9ee98","token": "storage"},
{"foreground": "8f9d6a","token": "string"},
{"foreground": "9b859d","token": "support"},
{"foreground": "7587a6","token": "variable"},
{"foreground": "d2a8a1","fontStyle": "italic underline","token": "invalid.deprecated"},
{"foreground": "f8f8f8","background": "562d56bf","token": "invalid.illegal"},
{"background": "b0b3ba14","token": "text source"},
{"background": "b1b3ba21","token": "text.html.ruby source"},
{"foreground": "9b5c2e","fontStyle": "italic","token": "entity.other.inherited-class"},
{"foreground": "daefa3","token": "string source"},
{"foreground": "ddf2a4","token": "string constant"},
{"foreground": "e9c062","token": "string.regexp"},
{"foreground": "cf7d34","token": "string.regexp constant.character.escape"},
{"foreground": "cf7d34","token": "string.regexp source.ruby.embedded"},
{"foreground": "cf7d34","token": "string.regexp string.regexp.arbitrary-repitition"},
{"foreground": "8a9a95","token": "string variable"},
{"foreground": "dad085","token": "support.function"},
{"foreground": "cf6a4c","token": "support.constant"},
{"foreground": "8996a8","token": "meta.preprocessor.c"},
{"foreground": "afc4db","token": "meta.preprocessor.c keyword"},
{"foreground": "494949","token": "meta.tag.sgml.doctype"},
{"foreground": "494949","token": "meta.tag.sgml.doctype entity"},
{"foreground": "494949","token": "meta.tag.sgml.doctype string"},
{"foreground": "494949","token": "meta.tag.preprocessor.xml"},
{"foreground": "494949","token": "meta.tag.preprocessor.xml entity"},
{"foreground": "494949","token": "meta.tag.preprocessor.xml string"},
{"foreground": "ac885b","token": "declaration.tag"},
{"foreground": "ac885b","token": "declaration.tag entity"},
{"foreground": "ac885b","token": "meta.tag"},
{"foreground": "ac885b","token": "meta.tag entity"},
{"foreground": "e0c589","token": "declaration.tag.inline"},
{"foreground": "e0c589","token": "declaration.tag.inline entity"},
{"foreground": "e0c589","token": "source entity.name.tag"},
{"foreground": "e0c589","token": "source entity.other.attribute-name"},
{"foreground": "e0c589","token": "meta.tag.inline"},
{"foreground": "e0c589","token": "meta.tag.inline entity"},
{"foreground": "cda869","token": "meta.selector.css entity.name.tag"},
{"foreground": "8f9d6a","token": "meta.selector.css entity.other.attribute-name.tag.pseudo-class"},
{"foreground": "8b98ab","token": "meta.selector.css entity.other.attribute-name.id"},
{"foreground": "9b703f","token": "meta.selector.css entity.other.attribute-name.class"},
{"foreground": "c5af75","token": "support.type.property-name.css"},
{"foreground": "f9ee98","token": "meta.property-group support.constant.property-value.css"},
{"foreground": "f9ee98","token": "meta.property-value support.constant.property-value.css"},
{"foreground": "8693a5","token": "meta.preprocessor.at-rule keyword.control.at-rule"},
{"foreground": "ca7840","token": "meta.property-value support.constant.named-color.css"},
{"foreground": "ca7840","token": "meta.property-value constant"},
{"foreground": "8f9d6a","token": "meta.constructor.argument.css"},
{"foreground": "f8f8f8","background": "0e2231","fontStyle": "italic","token": "meta.diff"},
{"foreground": "f8f8f8","background": "0e2231","fontStyle": "italic","token": "meta.diff.header"},
{"foreground": "f8f8f8","background": "0e2231","fontStyle": "italic","token": "meta.separator"},
{"foreground": "f8f8f8","background": "420e09","token": "markup.deleted"},
{"foreground": "f8f8f8","background": "4a410d","token": "markup.changed"},
{"foreground": "f8f8f8","background": "253b22","token": "markup.inserted"},
{"foreground": "f9ee98","token": "markup.list"},
{"foreground": "cf6a4c","token": "markup.heading"}
],
"colors": {
"editor.foreground": "#F8F8F8",
"editor.background": "#141414",
"editor.selectionBackground": "#DDF0FF33",
"editor.lineHighlightBackground": "#FFFFFF08",
"editorCursor.foreground": "#A7A7A7",
"editorWhitespace.foreground": "#FFFFFF40",
'diffEditor.insertedTextBackground': '#2ea04320',
'diffEditor.insertedLineBackground': '#2ea04326',
'diffEditor.removedTextBackground': '#f8514920',
'diffEditor.removedLineBackground': '#f8514920',
'diffEditor.insertedTextBorder': '#2ea04300',
'diffEditor.removedTextBorder': '#f8514900',
}
} as Monaco.editor.IStandaloneThemeData
export const editorThemes: Record = {
"all-hallows-eve": allHallowsEve,
"amy": amy,
"birds-of-paradise": birdsOfParadise,
"blackboard": blackboard,
"brilliance-black": brillianceBlack,
"brilliance-dull": brillianceDull,
"chrome-dev-tools": chromeDevTools,
"clouds-midnight": cloudsMidnight,
"clouds": clouds,
"cobalt": cobalt,
"dracula": dracula,
"dreamweaver": dreamweaver,
"espresso-libre": espressoLibre,
"github-dark": githubDark,
"github-light": githubLight,
"github": github,
"merbivore-soft": merbivoreSoft,
"monokai": monokai,
"night-owl": nightOwl,
"nord": nord,
"oceanic-next": oceanicNext,
"pastels-on-dark": pastelsOnDark,
"sunburst": sunburst,
"tomorrow-night-blue": tomorrowNightBlue,
"tomorrow-night-bright": tomorrowNightBright,
"tomorrow-night-eighties": tomorrowNightEighties,
"tomorrow-night": tomorrowNight,
"tomorrow": tomorrow,
"twilight": twilight,
}
================================================
FILE: www/app/Event.ts
================================================
///
import EventDispatcher from './dispatcher/EventDispatcher';
import * as Csrf from './Csrf';
let connected = false;
const pendingEvents: Record = {};
function connect(): void {
let url = '';
let location = window.location;
if (location.protocol === 'https:') {
url += 'wss';
} else {
url += 'ws';
}
url += '://' + location.host + '/event?csrf_token=' + Csrf.token;
let socket = new WebSocket(url);
socket.addEventListener('close', () => {
setTimeout(() => {
connect();
}, 500);
});
socket.addEventListener('message', (evt) => {
const eventData = JSON.parse(evt.data).data;
const eventId = JSON.stringify(eventData);
if (pendingEvents[eventId]) {
return;
}
pendingEvents[eventId] = eventData;
setTimeout(() => {
if (pendingEvents[eventId]) {
console.log(eventData);
EventDispatcher.dispatch(eventData);
delete pendingEvents[eventId];
}
}, 300);
});
}
export function init() {
if (connected) {
return;
}
connected = true;
connect();
}
================================================
FILE: www/app/EventEmitter.ts
================================================
///
import * as Events from 'events';
export default class EventEmitter extends Events.EventEmitter {
emitDefer(event: string | symbol, ...args: any[]): void {
setTimeout((): void => {
this.emit(event, ...args);
});
}
}
================================================
FILE: www/app/License.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Alert from './Alert';
import * as Csrf from './Csrf';
export let oracle = false;
export function save(): Promise {
return new Promise((resolve, reject): void => {
SuperAgent
.put('/license')
.send({
oracle: oracle,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save license state');
reject(err);
return;
}
resolve();
});
});
}
export function setOracle(state: boolean): void {
oracle = state;
}
================================================
FILE: www/app/Loader.ts
================================================
///
import Dispatcher from './dispatcher/Dispatcher';
import * as LoadingTypes from './types/LoadingTypes';
import * as MiscUtils from './utils/MiscUtils';
export default class Loader {
_id: string;
constructor() {
this._id = MiscUtils.uuid();
}
loading(): Loader {
Dispatcher.dispatch({
type: LoadingTypes.ADD,
data: {
id: this._id,
},
});
return this;
}
done(): Loader {
Dispatcher.dispatch({
type: LoadingTypes.DONE,
data: {
id: this._id,
},
});
return this;
}
}
================================================
FILE: www/app/References.d.ts
================================================
declare module '@novnc/novnc' {
export default class RFB {
constructor(target: HTMLDivElement, url: string, options?: any);
[key:string]: any;
}
}
================================================
FILE: www/app/Router.ts
================================================
///
import * as CompletionActions from './actions/CompletionActions';
import * as UserActions from './actions/UserActions';
import * as SessionActions from './actions/SessionActions';
import * as AuditActions from './actions/AuditActions';
import * as NodeActions from './actions/NodeActions';
import * as PolicyActions from './actions/PolicyActions';
import * as CertificateActions from './actions/CertificateActions';
import * as SecretActions from './actions/SecretActions';
import * as OrganizationActions from './actions/OrganizationActions';
import * as DatacenterActions from './actions/DatacenterActions';
import * as AlertActions from './actions/AlertActions';
import * as ZoneActions from './actions/ZoneActions';
import * as ShapeActions from './actions/ShapeActions';
import * as BlockActions from './actions/BlockActions';
import * as VpcActions from './actions/VpcActions';
import * as DomainActions from './actions/DomainActions';
import * as PlanActions from './actions/PlanActions';
import * as BalancerActions from './actions/BalancerActions';
import * as StorageActions from './actions/StorageActions';
import * as ImageActions from './actions/ImageActions';
import * as PoolActions from './actions/PoolActions';
import * as DiskActions from './actions/DiskActions';
import * as InstanceActions from './actions/InstanceActions';
import * as PodActions from './actions/PodActions';
import * as FirewallActions from './actions/FirewallActions';
import * as AuthorityActions from './actions/AuthorityActions';
import * as LogActions from './actions/LogActions';
import * as SettingsActions from './actions/SettingsActions';
import * as SubscriptionActions from './actions/SubscriptionActions';
export function setLocation(location: string) {
window.location.hash = location
let evt = new Event("router_update")
window.dispatchEvent(evt)
}
export function reload() {
let evt = new Event("router_update")
window.dispatchEvent(evt)
}
export function refresh(callback?: () => void) {
let pathname = window.location.hash.replace(/^#/, '');
CompletionActions.sync();
if (pathname === '/users') {
UserActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname.startsWith('/user/')) {
UserActions.reload().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
SessionActions.reload().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
AuditActions.reload().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/nodes') {
NodeActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/policies') {
SettingsActions.sync();
PolicyActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/certificates') {
CertificateActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/secrets') {
SecretActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/organizations') {
OrganizationActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/datacenters') {
DatacenterActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/zones') {
ZoneActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/shapes') {
ShapeActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/blocks') {
BlockActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/vpcs') {
VpcActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/domains') {
DomainActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/plans') {
PlanActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/balancers') {
BalancerActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/storages') {
StorageActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/images') {
ImageActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/pools') {
PoolActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/disks') {
DiskActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/instances') {
InstanceActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/pods') {
PodActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/firewalls') {
FirewallActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/authorities') {
AuthorityActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/alerts') {
AlertActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/logs') {
LogActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/settings') {
SettingsActions.sync().then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else if (pathname === '/subscription') {
SubscriptionActions.sync(true).then((): void => {
if (callback) {
callback()
}
}).catch((): void => {
if (callback) {
callback()
}
});
} else {
console.log(`Failed to match refresh ${pathname}`)
this.setState({
...this.state,
disabled: false,
});
}
}
================================================
FILE: www/app/Styles.tsx
================================================
///
import * as React from 'react';
import * as Theme from './Theme';
import * as Blueprint from '@blueprintjs/core';
interface Colors {
white: string;
black: string;
blue1: string;
blue2: string;
blue3: string;
blue4: string;
blue5: string;
darkGray1: string;
darkGray2: string;
darkGray3: string;
darkGray4: string;
darkGray5: string;
forest1: string;
forest2: string;
forest3: string;
forest4: string;
forest5: string;
gold1: string;
gold2: string;
gold3: string;
gold4: string;
gold5: string;
gray1: string;
gray2: string;
gray3: string;
gray4: string;
gray5: string;
green1: string;
green2: string;
green3: string;
green4: string;
green5: string;
indigo1: string;
indigo2: string;
indigo3: string;
indigo4: string;
indigo5: string;
lightGray1: string;
lightGray2: string;
lightGray3: string;
lightGray4: string;
lightGray5: string;
lime1: string;
lime2: string;
lime3: string;
lime4: string;
lime5: string;
orange1: string;
orange2: string;
orange3: string;
orange4: string;
orange5: string;
red1: string;
red2: string;
red3: string;
red4: string;
red5: string;
rose1: string;
rose2: string;
rose3: string;
rose4: string;
rose5: string;
sepia1: string;
sepia2: string;
sepia3: string;
sepia4: string;
sepia5: string;
turquoise1: string;
turquoise2: string;
turquoise3: string;
turquoise4: string;
turquoise5: string;
vermilion1: string;
vermilion2: string;
vermilion3: string;
vermilion4: string;
vermilion5: string;
violet1: string;
violet2: string;
violet3: string;
violet4: string;
violet5: string;
}
interface Styles {
colors: Colors;
}
export const colors = {
white: Blueprint.Colors.WHITE,
black: Blueprint.Colors.BLACK,
blue1: Blueprint.Colors.BLUE1,
blue2: Blueprint.Colors.BLUE2,
blue3: Blueprint.Colors.BLUE3,
blue4: Blueprint.Colors.BLUE4,
blue5: Blueprint.Colors.BLUE5,
darkGray1: Blueprint.Colors.DARK_GRAY1,
darkGray2: Blueprint.Colors.DARK_GRAY2,
darkGray3: Blueprint.Colors.DARK_GRAY3,
darkGray4: Blueprint.Colors.DARK_GRAY4,
darkGray5: Blueprint.Colors.DARK_GRAY5,
forest1: Blueprint.Colors.FOREST1,
forest2: Blueprint.Colors.FOREST2,
forest3: Blueprint.Colors.FOREST3,
forest4: Blueprint.Colors.FOREST4,
forest5: Blueprint.Colors.FOREST5,
gold1: Blueprint.Colors.GOLD1,
gold2: Blueprint.Colors.GOLD2,
gold3: Blueprint.Colors.GOLD3,
gold4: Blueprint.Colors.GOLD4,
gold5: Blueprint.Colors.GOLD5,
gray1: Blueprint.Colors.GRAY1,
gray2: Blueprint.Colors.GRAY2,
gray3: Blueprint.Colors.GRAY3,
gray4: Blueprint.Colors.GRAY4,
gray5: Blueprint.Colors.GRAY5,
green1: Blueprint.Colors.GREEN1,
green2: Blueprint.Colors.GREEN2,
green3: Blueprint.Colors.GREEN3,
green4: Blueprint.Colors.GREEN4,
green5: Blueprint.Colors.GREEN5,
indigo1: Blueprint.Colors.INDIGO1,
indigo2: Blueprint.Colors.INDIGO2,
indigo3: Blueprint.Colors.INDIGO3,
indigo4: Blueprint.Colors.INDIGO4,
indigo5: Blueprint.Colors.INDIGO5,
lightGray1: Blueprint.Colors.LIGHT_GRAY1,
lightGray2: Blueprint.Colors.LIGHT_GRAY2,
lightGray3: Blueprint.Colors.LIGHT_GRAY3,
lightGray4: Blueprint.Colors.LIGHT_GRAY4,
lightGray5: Blueprint.Colors.LIGHT_GRAY5,
lime1: Blueprint.Colors.LIME1,
lime2: Blueprint.Colors.LIME2,
lime3: Blueprint.Colors.LIME3,
lime4: Blueprint.Colors.LIME4,
lime5: Blueprint.Colors.LIME5,
orange1: Blueprint.Colors.ORANGE1,
orange2: Blueprint.Colors.ORANGE2,
orange3: Blueprint.Colors.ORANGE3,
orange4: Blueprint.Colors.ORANGE4,
orange5: Blueprint.Colors.ORANGE5,
red1: Blueprint.Colors.RED1,
red2: Blueprint.Colors.RED2,
red3: Blueprint.Colors.RED3,
red4: Blueprint.Colors.RED4,
red5: Blueprint.Colors.RED5,
rose1: Blueprint.Colors.ROSE1,
rose2: Blueprint.Colors.ROSE2,
rose3: Blueprint.Colors.ROSE3,
rose4: Blueprint.Colors.ROSE4,
rose5: Blueprint.Colors.ROSE5,
sepia1: Blueprint.Colors.SEPIA1,
sepia2: Blueprint.Colors.SEPIA2,
sepia3: Blueprint.Colors.SEPIA3,
sepia4: Blueprint.Colors.SEPIA4,
sepia5: Blueprint.Colors.SEPIA5,
turquoise1: Blueprint.Colors.TURQUOISE1,
turquoise2: Blueprint.Colors.TURQUOISE2,
turquoise3: Blueprint.Colors.TURQUOISE3,
turquoise4: Blueprint.Colors.TURQUOISE4,
turquoise5: Blueprint.Colors.TURQUOISE5,
vermilion1: Blueprint.Colors.VERMILION1,
vermilion2: Blueprint.Colors.VERMILION2,
vermilion3: Blueprint.Colors.VERMILION3,
vermilion4: Blueprint.Colors.VERMILION4,
vermilion5: Blueprint.Colors.VERMILION5,
violet1: Blueprint.Colors.VIOLET1,
violet2: Blueprint.Colors.VIOLET2,
violet3: Blueprint.Colors.VIOLET3,
violet4: Blueprint.Colors.VIOLET4,
violet5: Blueprint.Colors.VIOLET5,
};
export const fixedHeight = 1000
export const headerShift = (): string => {
if (Theme.theme === "dark") {
return "rgba(0, 0, 0, 0.13)"
}
return "rgba(0, 0, 0, 0.04)"
}
================================================
FILE: www/app/Theme.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Alert from './Alert';
import * as Csrf from './Csrf';
import * as MiscUtils from './utils/MiscUtils';
import * as EditorThemes from './EditorThemes';
import * as Monaco from "monaco-editor"
export interface Callback {
(): void;
}
let callbacks: Set = new Set();
export let theme = 'dark';
export let themeVer = 5;
let editorThemeName = '';
export const monospaceSize = "12px"
export const monospaceFont = "Consolas, Menlo, 'Roboto Mono', 'DejaVu Sans Mono'"
export const monospaceWeight = "500"
export function save(): Promise {
return new Promise((resolve, reject): void => {
SuperAgent
.put('/theme')
.send({
theme: theme + `-${themeVer}`,
editor_theme: editorThemeName,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save theme');
reject(err);
return;
}
resolve();
});
});
}
export function themeVer3(): void {
const blueprintTheme3 = document.getElementById(
"blueprint3-theme") as HTMLLinkElement
const blueprintTheme5 = document.getElementById(
"blueprint5-theme") as HTMLLinkElement
blueprintTheme3.disabled = false;
blueprintTheme5.disabled = true;
if (theme === "dark") {
document.body.className = 'bp3-theme bp5-dark';
document.documentElement.className = 'dark3-scroll bp5-focus-disabled';
} else {
document.body.className = 'bp3-theme';
document.documentElement.className = 'bp5-focus-disabled';
}
themeVer = 3;
}
export function themeVer5(): void {
const blueprintTheme3 = document.getElementById(
"blueprint3-theme") as HTMLLinkElement
const blueprintTheme5 = document.getElementById(
"blueprint5-theme") as HTMLLinkElement
blueprintTheme3.disabled = true;
blueprintTheme5.disabled = false;
if (theme === "dark") {
document.body.className = 'bp5-dark';
document.documentElement.className = 'dark5-scroll bp5-focus-disabled';
} else {
document.body.className = '';
document.documentElement.className = 'bp5-focus-disabled';
}
themeVer = 5;
}
export function light(): void {
theme = 'light';
if (themeVer === 3) {
document.body.className = 'bp3-theme';
document.documentElement.className = 'bp5-focus-disabled';
} else {
document.body.className = '';
document.documentElement.className = 'bp5-focus-disabled';
}
callbacks.forEach((callback: Callback): void => {
callback();
});
}
export function dark(): void {
theme = 'dark';
if (themeVer === 3) {
document.body.className = 'bp3-theme bp5-dark';
document.documentElement.className = 'dark3-scroll bp5-focus-disabled';
} else {
document.body.className = 'bp5-dark';
document.documentElement.className = 'dark5-scroll bp5-focus-disabled';
}
callbacks.forEach((callback: Callback): void => {
callback();
});
}
export function toggle(ver3: boolean): void {
if (theme === "dark") {
light();
if (ver3) {
themeVer3();
} else {
themeVer5();
}
} else if (theme === "light") {
dark();
if (ver3) {
themeVer3();
} else {
themeVer5();
}
}
}
export function getEditorTheme(): string {
if (!editorThemeName) {
if (theme === "light") {
return "github-light";
} else {
return "github-dark";
}
}
return editorThemeName
}
export function setEditorTheme(name: string) {
editorThemeName = name
callbacks.forEach((callback: Callback): void => {
callback();
});
}
export function addChangeListener(callback: Callback): void {
callbacks.add(callback);
}
export function removeChangeListener(callback: () => void): void {
callbacks.delete(callback);
}
export let editorThemeNames: Record = {}
for (let themeName in EditorThemes.editorThemes) {
let editorTheme = EditorThemes.editorThemes[themeName]
Monaco.editor.defineTheme(themeName, editorTheme)
let formattedThemeName = MiscUtils.titleCase(
themeName.replaceAll("-", " "))
editorThemeNames[themeName] = formattedThemeName
}
================================================
FILE: www/app/actions/AlertActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import AlertsStore from '../stores/AlertsStore';
import * as AlertTypes from '../types/AlertTypes';
import * as MiscUtils from '../utils/MiscUtils';
import CompletionStore from "../stores/CompletionStore";
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/alert')
.query({
...AlertsStore.filter,
page: AlertsStore.page,
page_count: AlertsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load alerts');
reject(err);
return;
}
Dispatcher.dispatch({
type: AlertTypes.SYNC,
data: {
alerts: res.body.alerts,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: AlertTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: AlertTypes.Filter): Promise {
Dispatcher.dispatch({
type: AlertTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(alert: AlertTypes.Alert): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/alert/' + alert.id)
.send(alert)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save alert');
reject(err);
return;
}
resolve();
});
});
}
export function create(alert: AlertTypes.Alert): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/alert')
.send(alert)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create alert');
reject(err);
return;
}
resolve();
});
});
}
export function remove(alertId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/alert/' + alertId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (err) {
Alert.errorRes(res, 'Failed to delete alerts');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(alertIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/alert')
.send(alertIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete alerts');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: AlertTypes.AlertDispatch) => {
switch (action.type) {
case AlertTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/AuditActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Constants from '../Constants';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as AuditTypes from '../types/AuditTypes';
import * as MiscUtils from '../utils/MiscUtils';
import AuditsStore from '../stores/AuditsStore';
let syncId: string;
export function load(userId: string): Promise {
if (!userId) {
return Promise.resolve();
}
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/audit/' + userId)
.query({
page: AuditsStore.page,
page_count: AuditsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load audits');
reject(err);
return;
}
Dispatcher.dispatch({
type: AuditTypes.SYNC,
data: {
userId: userId,
audits: res.body.audits,
count: res.body.count,
},
});
resolve();
});
});
}
export function reload(): Promise {
return load(AuditsStore.userId);
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: AuditTypes.TRAVERSE,
data: {
page: page,
},
});
return reload();
}
EventDispatcher.register((action: AuditTypes.AuditDispatch) => {
switch (action.type) {
case AuditTypes.CHANGE:
if (!Constants.user) {
reload();
}
break;
}
});
================================================
FILE: www/app/actions/AuthorityActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as AuthorityTypes from '../types/AuthorityTypes';
import AuthoritiesStore from '../stores/AuthoritiesStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
import * as Constants from "../Constants";
let syncId: string;
let syncNamesId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/authority')
.query({
...AuthoritiesStore.filter,
page: AuthoritiesStore.page,
page_count: AuthoritiesStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load authorities');
reject(err);
return;
}
Dispatcher.dispatch({
type: AuthorityTypes.SYNC,
data: {
authorities: res.body.authorities,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncNames(): Promise {
let curSyncId = MiscUtils.uuid();
syncNamesId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/authority')
.query({
names: true,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncNamesId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load authority names');
reject(err);
return;
}
Dispatcher.dispatch({
type: AuthorityTypes.SYNC_NAMES,
data: {
authorities: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: AuthorityTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: AuthorityTypes.Filter): Promise {
Dispatcher.dispatch({
type: AuthorityTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(authority: AuthorityTypes.Authority): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/authority/' + authority.id)
.send(authority)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save authority');
reject(err);
return;
}
resolve();
});
});
}
export function create(authority: AuthorityTypes.Authority): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/authority')
.send(authority)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create authority');
reject(err);
return;
}
resolve();
});
});
}
export function remove(authorityId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/authority/' + authorityId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete authority');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(authorityIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/authority')
.send(authorityIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete authorities');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: AuthorityTypes.AuthorityDispatch) => {
switch (action.type) {
case AuthorityTypes.CHANGE:
if (!Constants.user) {
sync();
}
break;
}
});
================================================
FILE: www/app/actions/BalancerActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as BalancerTypes from '../types/BalancerTypes';
import BalancersStore from '../stores/BalancersStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/balancer')
.query({
...BalancersStore.filter,
page: BalancersStore.page,
page_count: BalancersStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load balancers');
reject(err);
return;
}
Dispatcher.dispatch({
type: BalancerTypes.SYNC,
data: {
balancers: res.body.balancers,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: BalancerTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: BalancerTypes.Filter): Promise {
Dispatcher.dispatch({
type: BalancerTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(balancer: BalancerTypes.Balancer): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/balancer/' + balancer.id)
.send(balancer)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save balancer');
reject(err);
return;
}
resolve();
});
});
}
export function create(balancer: BalancerTypes.Balancer): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/balancer')
.send(balancer)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create balancer');
reject(err);
return;
}
resolve();
});
});
}
export function remove(balancerId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/balancer/' + balancerId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete balancer');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(balancerIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/balancer')
.send(balancerIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete balancers');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: BalancerTypes.BalancerDispatch) => {
switch (action.type) {
case BalancerTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/BlockActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as BlockTypes from '../types/BlockTypes';
import BlocksStore from '../stores/BlocksStore';
import * as MiscUtils from '../utils/MiscUtils';
import * as Constants from "../Constants";
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/block')
.query({
...BlocksStore.filter,
page: BlocksStore.page,
page_count: BlocksStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load blocks');
reject(err);
return;
}
Dispatcher.dispatch({
type: BlockTypes.SYNC,
data: {
blocks: res.body.blocks,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: BlockTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: BlockTypes.Filter): Promise {
Dispatcher.dispatch({
type: BlockTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(block: BlockTypes.Block): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/block/' + block.id)
.send(block)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save block');
reject(err);
return;
}
resolve();
});
});
}
export function create(block: BlockTypes.Block): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/block')
.send(block)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create block');
reject(err);
return;
}
resolve();
});
});
}
export function remove(blockId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/block/' + blockId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete blocks');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(blockIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/block')
.send(blockIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete blocks');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: BlockTypes.BlockDispatch) => {
switch (action.type) {
case BlockTypes.CHANGE:
if (!Constants.user) {
sync();
}
break;
}
});
================================================
FILE: www/app/actions/CertificateActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as CertificateTypes from '../types/CertificateTypes';
import CertificatesStore from '../stores/CertificatesStore';
import * as MiscUtils from '../utils/MiscUtils';
import CompletionStore from "../stores/CompletionStore";
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/certificate')
.query({
...CertificatesStore.filter,
page: CertificatesStore.page,
page_count: CertificatesStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load certificates');
reject(err);
return;
}
Dispatcher.dispatch({
type: CertificateTypes.SYNC,
data: {
certificates: res.body.certificates,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: CertificateTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: CertificateTypes.Filter): Promise {
Dispatcher.dispatch({
type: CertificateTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(cert: CertificateTypes.Certificate): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/certificate/' + cert.id)
.send(cert)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save certificate');
reject(err);
return;
}
resolve();
});
});
}
export function create(cert: CertificateTypes.Certificate): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/certificate')
.send(cert)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create certificate');
reject(err);
return;
}
resolve();
});
});
}
export function remove(certId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/certificate/' + certId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete certificates');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(certificateIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/certificate')
.send(certificateIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete certificates');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: CertificateTypes.CertificateDispatch) => {
switch (action.type) {
case CertificateTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/CompletionActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Constants from '../Constants';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as GlobalTypes from '../types/GlobalTypes';
import * as CompletionTypes from '../types/CompletionTypes';
import CompletionStore from '../stores/CompletionStore';
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
let lastSyncTime: number | null = null;
let syncInProgress: boolean = false;
export function sync(): Promise {
if (syncInProgress) {
return Promise.resolve();
}
syncInProgress = true;
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
return new Promise((resolve, reject): void => {
try {
SuperAgent
.get('/completion')
.query({
...CompletionStore.filter,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization || "")
.end((err: any, res: SuperAgent.Response): void => {
syncInProgress = false;
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load completion data');
reject(err);
return;
}
Dispatcher.dispatch({
type: CompletionTypes.SYNC,
data: {
completion: res.body,
},
});
lastSyncTime = Date.now();
resolve();
});
} catch (e) {
syncInProgress = false;
reject(e);
}
});
}
export function lastSync(): number | null {
return lastSyncTime;
}
export function filter(filt: CompletionTypes.Filter): Promise {
Dispatcher.dispatch({
type: CompletionTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function setUserOrganization(userOrg: string): void {
Dispatcher.dispatch({
type: GlobalTypes.RESET,
data: {
organization: userOrg,
},
});
Dispatcher.dispatch({
type: GlobalTypes.RELOAD,
data: {
organization: userOrg,
},
});
}
EventDispatcher.register((action: CompletionTypes.CompletionDispatch) => {
switch (action.type) {
case CompletionTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/DatacenterActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as DatacenterTypes from '../types/DatacenterTypes';
import DatacentersStore from '../stores/DatacentersStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
import * as Constants from "../Constants";
let syncId: string;
let syncNamesId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/datacenter')
.query({
...DatacentersStore.filter,
page: DatacentersStore.page,
page_count: DatacentersStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load datacenters');
reject(err);
return;
}
Dispatcher.dispatch({
type: DatacenterTypes.SYNC,
data: {
datacenters: res.body.datacenters,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncNames(): Promise {
let curSyncId = MiscUtils.uuid();
syncNamesId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/datacenter')
.query({
names: true,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncNamesId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load datacenter names');
reject(err);
return;
}
Dispatcher.dispatch({
type: DatacenterTypes.SYNC_NAMES,
data: {
secrets: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: DatacenterTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: DatacenterTypes.Filter): Promise {
Dispatcher.dispatch({
type: DatacenterTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(datacenter: DatacenterTypes.Datacenter): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/datacenter/' + datacenter.id)
.send(datacenter)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save datacenter');
reject(err);
return;
}
resolve();
});
});
}
export function create(datacenter: DatacenterTypes.Datacenter): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/datacenter')
.send(datacenter)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create datacenter');
reject(err);
return;
}
resolve();
});
});
}
export function remove(datacenterId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/datacenter/' + datacenterId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete datacenters');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(datacenterIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/datacenter')
.send(datacenterIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete datacenters');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: DatacenterTypes.DatacenterDispatch) => {
switch (action.type) {
case DatacenterTypes.CHANGE:
if (!Constants.user) {
sync();
}
break;
}
});
================================================
FILE: www/app/actions/DeviceActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as DeviceTypes from '../types/DeviceTypes';
import * as MiscUtils from '../utils/MiscUtils';
import DevicesStore from '../stores/DevicesStore';
import * as PolicyTypes from "../types/PolicyTypes";
let syncId: string;
export function load(userId: string): Promise {
if (!userId) {
return Promise.resolve();
}
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/device/' + userId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load devices');
reject(err);
return;
}
Dispatcher.dispatch({
type: DeviceTypes.SYNC,
data: {
userId: userId,
devices: res.body,
},
});
resolve();
});
});
}
export function reload(): Promise {
return load(DevicesStore.userId);
}
export function create(device: DeviceTypes.Device): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/device')
.send(device)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create device');
reject(err);
return;
}
resolve();
});
});
}
export function testAlert(deviceId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/device/' + deviceId + '/alert')
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to send test alert');
reject(err);
return;
}
resolve();
});
});
}
export function commit(device: DeviceTypes.Device): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/device/' + device.id)
.send(device)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save device');
reject(err);
return;
}
resolve();
});
});
}
export function remove(deviceId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/device/' + deviceId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete device');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: DeviceTypes.DeviceDispatch) => {
switch (action.type) {
case DeviceTypes.CHANGE:
reload();
break;
}
});
================================================
FILE: www/app/actions/DiskActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as DiskTypes from '../types/DiskTypes';
import DisksStore from '../stores/DisksStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/disk')
.query({
...DisksStore.filter,
page: DisksStore.page,
page_count: DisksStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load disks');
reject(err);
return;
}
Dispatcher.dispatch({
type: DiskTypes.SYNC,
data: {
disks: res.body.disks,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: DiskTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: DiskTypes.Filter): Promise {
Dispatcher.dispatch({
type: DiskTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(disk: DiskTypes.Disk): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/disk/' + disk.id)
.send(disk)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save disk');
reject(err);
return;
}
resolve();
});
});
}
export function create(disk: DiskTypes.Disk): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/disk')
.send(disk)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create disk');
reject(err);
return;
}
resolve();
});
});
}
export function remove(diskId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/disk/' + diskId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete disk');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(diskIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/disk')
.send(diskIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete disks');
reject(err);
return;
}
resolve();
});
});
}
export function forceRemoveMulti(diskIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/disk')
.query({
force: true,
})
.send(diskIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete disks');
reject(err);
return;
}
resolve();
});
});
}
export function updateMulti(diskIds: string[],
action: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/disk')
.send({
"ids": diskIds,
"action": action,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to update disks');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: DiskTypes.DiskDispatch) => {
switch (action.type) {
case DiskTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/DomainActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as DomainTypes from '../types/DomainTypes';
import DomainsStore from '../stores/DomainsStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
let syncNamesId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/domain')
.query({
...DomainsStore.filter,
page: DomainsStore.page,
page_count: DomainsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load domains');
reject(err);
return;
}
Dispatcher.dispatch({
type: DomainTypes.SYNC,
data: {
domains: res.body.domains,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncName(): Promise {
let curSyncId = MiscUtils.uuid();
syncNamesId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/domain')
.query({
names: true,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncNamesId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load domain names');
reject(err);
return;
}
Dispatcher.dispatch({
type: DomainTypes.SYNC_NAME,
data: {
domains: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: DomainTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: DomainTypes.Filter): Promise {
Dispatcher.dispatch({
type: DomainTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(domain: DomainTypes.Domain): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/domain/' + domain.id)
.send(domain)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save domain');
reject(err);
return;
}
resolve();
});
});
}
export function create(domain: DomainTypes.Domain): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/domain')
.send(domain)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create domain');
reject(err);
return;
}
resolve();
});
});
}
export function remove(domainId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/domain/' + domainId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete domain');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(domainIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/domain')
.send(domainIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete domains');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: DomainTypes.DomainDispatch) => {
switch (action.type) {
case DomainTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/FirewallActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as FirewallTypes from '../types/FirewallTypes';
import FirewallsStore from '../stores/FirewallsStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/firewall')
.query({
...FirewallsStore.filter,
page: FirewallsStore.page,
page_count: FirewallsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load firewalls');
reject(err);
return;
}
Dispatcher.dispatch({
type: FirewallTypes.SYNC,
data: {
firewalls: res.body.firewalls,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: FirewallTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: FirewallTypes.Filter): Promise {
Dispatcher.dispatch({
type: FirewallTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(firewall: FirewallTypes.Firewall): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/firewall/' + firewall.id)
.send(firewall)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save firewall');
reject(err);
return;
}
resolve();
});
});
}
export function create(firewall: FirewallTypes.Firewall): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/firewall')
.send(firewall)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create firewall');
reject(err);
return;
}
resolve();
});
});
}
export function remove(firewallId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/firewall/' + firewallId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete firewall');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(firewallIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/firewall')
.send(firewallIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete firewalls');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: FirewallTypes.FirewallDispatch) => {
switch (action.type) {
case FirewallTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/ImageActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as ImageTypes from '../types/ImageTypes';
import ImagesStore from '../stores/ImagesStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/image')
.query({
...ImagesStore.filter,
page: ImagesStore.page,
page_count: ImagesStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load images');
reject(err);
return;
}
Dispatcher.dispatch({
type: ImageTypes.SYNC,
data: {
images: res.body.images,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncDatacenter(datacenter: string): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
if (!datacenter) {
Dispatcher.dispatch({
type: ImageTypes.SYNC_DATACENTER,
data: {
images: [],
},
});
return Promise.resolve();
}
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/image')
.query({
datacenter: datacenter,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load images names');
reject(err);
return;
}
Dispatcher.dispatch({
type: ImageTypes.SYNC_DATACENTER,
data: {
images: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: ImageTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: ImageTypes.Filter): Promise {
Dispatcher.dispatch({
type: ImageTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(image: ImageTypes.Image): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/image/' + image.id)
.send(image)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save image');
reject(err);
return;
}
resolve();
});
});
}
export function create(image: ImageTypes.Image): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/image')
.send(image)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create image');
reject(err);
return;
}
resolve();
});
});
}
export function remove(imageId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/image/' + imageId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete image');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(imageIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/image')
.send(imageIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete images');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: ImageTypes.ImageDispatch) => {
switch (action.type) {
case ImageTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/InstanceActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as InstanceTypes from '../types/InstanceTypes';
import InstancesStore from '../stores/InstancesStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/instance')
.query({
...InstancesStore.filter,
page: InstancesStore.page,
page_count: InstancesStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load instances');
reject(err);
return;
}
Dispatcher.dispatch({
type: InstanceTypes.SYNC,
data: {
instances: res.body.instances,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: InstanceTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: InstanceTypes.Filter): Promise {
Dispatcher.dispatch({
type: InstanceTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(instance: InstanceTypes.Instance): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/instance/' + instance.id)
.send(instance)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save instance');
reject(err);
return;
}
resolve();
});
});
}
export function create(instance: InstanceTypes.Instance): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/instance')
.send(instance)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create instance');
reject(err);
return;
}
resolve();
});
});
}
export function remove(instanceId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/instance/' + instanceId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete instance');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(instanceIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/instance')
.send(instanceIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete instances');
reject(err);
return;
}
resolve();
});
});
}
export function forceRemoveMulti(instanceIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/instance')
.query({
force: true,
})
.send(instanceIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to force delete instances');
reject(err);
return;
}
resolve();
});
});
}
export function updateMulti(instanceIds: string[],
action: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/instance')
.send({
"ids": instanceIds,
"action": action,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to update instances');
reject(err);
return;
}
resolve();
});
});
}
export function syncNode(node: string, pool: string): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let scope: string;
let query: {[key: string]: string};
if (node) {
scope = node;
query = {
node_names: node,
};
} else {
scope = pool;
query = {
pool_names: pool,
};
}
if (!scope) {
return Promise.resolve();
}
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/instance')
.query(query)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load instance names');
reject(err);
return;
}
Dispatcher.dispatch({
type: InstanceTypes.SYNC_NODE,
data: {
scope: scope,
instances: res.body,
},
});
resolve();
});
});
}
EventDispatcher.register((action: InstanceTypes.InstanceDispatch) => {
switch (action.type) {
case InstanceTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/LogActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import * as Constants from '../Constants';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as LogTypes from '../types/LogTypes';
import LogsStore from '../stores/LogsStore';
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/log')
.query({
...LogsStore.filter,
page: LogsStore.page,
page_count: LogsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load logs');
reject(err);
return;
}
Dispatcher.dispatch({
type: LogTypes.SYNC,
data: {
logs: res.body.logs,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: LogTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: LogTypes.Filter): Promise {
Dispatcher.dispatch({
type: LogTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
EventDispatcher.register((action: LogTypes.LogDispatch) => {
switch (action.type) {
case LogTypes.CHANGE:
if (!Constants.user && window.location.hash.indexOf('/logs') !== -1) {
sync();
}
break;
}
});
================================================
FILE: www/app/actions/NodeActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as NodeTypes from '../types/NodeTypes';
import NodesStore from '../stores/NodesStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
let syncZonesId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/node')
.query({
...NodesStore.filter,
page: NodesStore.page,
page_count: NodesStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load nodes');
reject(err);
return;
}
Dispatcher.dispatch({
type: NodeTypes.SYNC,
data: {
nodes: res.body.nodes,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncZone(zone: string): Promise {
let curSyncId = MiscUtils.uuid();
syncZonesId = curSyncId;
if (!zone) {
Dispatcher.dispatch({
type: NodeTypes.SYNC_ZONE,
data: {
nodes: [],
},
});
return Promise.resolve();
}
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/node')
.query({
names: true,
zone: zone,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncZonesId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load nodes names');
reject(err);
return;
}
Dispatcher.dispatch({
type: NodeTypes.SYNC_ZONE,
data: {
nodes: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: NodeTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: NodeTypes.Filter): Promise {
Dispatcher.dispatch({
type: NodeTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(node: NodeTypes.Node): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/node/' + node.id)
.send(node)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save node');
reject(err);
return;
}
resolve();
});
});
}
export function operation(nodeId: string, operation: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/node/' + nodeId + '/' + operation)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to update node');
reject(err);
return;
}
resolve();
});
});
}
export function init(nodeId: string,
data: NodeTypes.NodeInit): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/node/' + nodeId + '/init')
.send(data)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to update node');
reject(err);
return;
}
resolve();
});
});
}
export function create(node: NodeTypes.Node): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/node')
.send(node)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create node');
reject(err);
return;
}
resolve();
});
});
}
export function remove(nodeId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/node/' + nodeId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete nodes');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: NodeTypes.NodeDispatch) => {
switch (action.type) {
case NodeTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/OrganizationActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as OrganizationTypes from '../types/OrganizationTypes';
import OrganizationsStore from '../stores/OrganizationsStore';
import * as MiscUtils from '../utils/MiscUtils';
import * as Constants from "../Constants";
let syncId: string;
export function sync(): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/organization')
.query({
...OrganizationsStore.filter,
page: OrganizationsStore.page,
page_count: OrganizationsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load organizations');
reject(err);
return;
}
Dispatcher.dispatch({
type: OrganizationTypes.SYNC,
data: {
organizations: res.body.organizations,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: OrganizationTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: OrganizationTypes.Filter): Promise {
Dispatcher.dispatch({
type: OrganizationTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(org: OrganizationTypes.Organization): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/organization/' + org.id)
.send(org)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save organization');
reject(err);
return;
}
resolve();
});
});
}
export function create(org: OrganizationTypes.Organization): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/organization')
.send(org)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create organization');
reject(err);
return;
}
resolve();
});
});
}
export function remove(orgId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/organization/' + orgId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete organizations');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(organizationIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/organization')
.send(organizationIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete organizations');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: OrganizationTypes.OrganizationDispatch) => {
switch (action.type) {
case OrganizationTypes.CHANGE:
if (!Constants.user) {
sync();
}
break;
}
});
================================================
FILE: www/app/actions/PlanActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as PlanTypes from '../types/PlanTypes';
import PlansStore from '../stores/PlansStore';
import CompletionStore from "../stores/CompletionStore";
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
let syncNamesId: string;
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/plan')
.query({
...PlansStore.filter,
page: PlansStore.page,
page_count: PlansStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load plans');
reject(err);
return;
}
Dispatcher.dispatch({
type: PlanTypes.SYNC,
data: {
plans: res.body.plans,
count: res.body.count,
},
});
resolve();
});
});
}
export function syncName(): Promise {
let curSyncId = MiscUtils.uuid();
syncNamesId = curSyncId;
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.get('/plan')
.query({
names: true,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncNamesId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load plan names');
reject(err);
return;
}
Dispatcher.dispatch({
type: PlanTypes.SYNC_NAME,
data: {
plans: res.body,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: PlanTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: PlanTypes.Filter): Promise {
Dispatcher.dispatch({
type: PlanTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(plan: PlanTypes.Plan): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/plan/' + plan.id)
.send(plan)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save plan');
reject(err);
return;
}
resolve();
});
});
}
export function create(plan: PlanTypes.Plan): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/plan')
.send(plan)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create plan');
reject(err);
return;
}
resolve();
});
});
}
export function remove(planId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/plan/' + planId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete plan');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(planIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/plan')
.send(planIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete plans');
reject(err);
return;
}
resolve();
});
});
}
EventDispatcher.register((action: PlanTypes.PlanDispatch) => {
switch (action.type) {
case PlanTypes.CHANGE:
sync();
break;
}
});
================================================
FILE: www/app/actions/PodActions.ts
================================================
///
import * as SuperAgent from 'superagent';
import Dispatcher from '../dispatcher/Dispatcher';
import EventDispatcher from '../dispatcher/EventDispatcher';
import * as Alert from '../Alert';
import * as Csrf from '../Csrf';
import Loader from '../Loader';
import * as PodTypes from '../types/PodTypes';
import CompletionStore from "../stores/CompletionStore";
import PodsStore from '../stores/PodsStore';
import * as MiscUtils from '../utils/MiscUtils';
let syncId: string;
let syncUnitId: string;
let lastPodId: string;
let lastUnitId: string
let dataSyncReqs: {[key: string]: SuperAgent.Request} = {};
export function sync(noLoading?: boolean): Promise {
let curSyncId = MiscUtils.uuid();
syncId = curSyncId;
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
SuperAgent
.get('/pod')
.query({
...PodsStore.filter,
page: PodsStore.page,
page_count: PodsStore.pageCount,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncId) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load pods');
reject(err);
return;
}
Dispatcher.dispatch({
type: PodTypes.SYNC,
data: {
pods: res.body.pods,
count: res.body.count,
},
});
resolve();
});
});
}
export function traverse(page: number): Promise {
Dispatcher.dispatch({
type: PodTypes.TRAVERSE,
data: {
page: page,
},
});
return sync();
}
export function filter(filt: PodTypes.Filter): Promise {
Dispatcher.dispatch({
type: PodTypes.FILTER,
data: {
filter: filt,
},
});
return sync();
}
export function commit(pod: PodTypes.Pod): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/pod/' + pod.id)
.send(pod)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save pod');
reject(err);
return;
}
resolve();
});
});
}
export function commitDeploy(pod: PodTypes.Pod,
resync?: boolean): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/pod/' + pod.id + "/deploy")
.send(pod)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save pod');
reject(err);
return;
}
if (resync) {
sync(true)
}
resolve();
});
});
}
export function commitDrafts(pod: PodTypes.Pod,
resync?: boolean): Promise {
return new Promise((resolve, reject): void => {
SuperAgent
.put('/pod/' + pod.id + "/drafts")
.send(pod)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save pod');
reject(err);
return;
}
if (resync) {
sync(true)
}
resolve();
});
});
}
export function create(pod: PodTypes.Pod): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/pod')
.send(pod)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create pod');
reject(err);
return;
}
resolve();
});
});
}
export function remove(podId: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/pod/' + podId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete pod');
reject(err);
return;
}
resolve();
});
});
}
export function removeMulti(podIds: string[]): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.delete('/pod')
.send(podIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to delete pods');
reject(err);
return;
}
resolve();
});
});
}
export function syncUnit(podId?: string, unitId?: string): Promise {
if (!podId) {
podId = lastPodId
} else {
lastPodId = podId
}
if (!unitId) {
unitId = lastUnitId
} else {
lastUnitId = unitId
}
if (!podId || !unitId) {
return Promise.resolve();
}
let curSyncId = MiscUtils.uuid();
syncUnitId = curSyncId;
return new Promise((resolve, reject): void => {
SuperAgent
.get('/pod/' + podId + "/unit/" + unitId)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (curSyncId !== syncUnitId || (res && res.status === 404)) {
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load pod unit');
reject(err);
return;
}
Dispatcher.dispatch({
type: PodTypes.SYNC_UNIT,
data: {
unit: res.body,
},
});
resolve();
});
});
}
export function deployUnit(podId: string, unitId: string,
specId: string, count: number): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.post('/pod/' + podId + "/unit/" + unitId + "/deployment")
.send({
count: count,
spec: specId,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to create deployments');
reject(err);
return;
}
resolve();
});
});
}
export function updateMultiUnitAction(podId: string, unitId: string,
deploymentIds: string[], action: string, commit?: string): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/pod/' + podId + "/unit/" + unitId + "/deployment")
.query({
action: action,
commit: commit,
})
.send(deploymentIds)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to modify deployments');
reject(err);
return;
}
resolve();
});
});
}
export function commitDeployment(deply: PodTypes.Deployment): Promise {
let loader = new Loader().loading();
return new Promise((resolve, reject): void => {
SuperAgent
.put('/pod/' + deply.pod + "/unit/" + deply.unit +
"/deployment/" + deply.id)
.send(deply)
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.end((err: any, res: SuperAgent.Response): void => {
loader.done();
if (res && res.status === 401) {
window.location.href = '/login';
resolve();
return;
}
if (err) {
Alert.errorRes(res, 'Failed to save deployment');
reject(err);
return;
}
resolve();
});
});
}
export function log(deply: PodTypes.Deployment,
resource: string, noLoading?: boolean): Promise {
let curDataSyncId = MiscUtils.uuid();
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise((resolve, reject): void => {
let req = SuperAgent.get('/pod/' + deply.pod +
"/unit/" + deply.unit + "/deployment/" + deply.id + "/log")
.query({
resource: resource,
})
.set('Accept', 'application/json')
.set('Csrf-Token', Csrf.token)
.set('Organization', CompletionStore.userOrganization)
.on('abort', () => {
if (loader) {
loader.done();
}
resolve(null);
});
dataSyncReqs[curDataSyncId] = req;
req.end((err: any, res: SuperAgent.Response): void => {
delete dataSyncReqs[curDataSyncId];
if (loader) {
loader.done();
}
if (res && res.status === 401) {
window.location.href = '/login';
resolve(null);
return;
}
if (err) {
Alert.errorRes(res, 'Failed to load check log');
reject(err);
return;
}
resolve(res.body);
});
});
}
export function syncSpecs(podId: string, unitId: string, page: number,
noLoading?: boolean): Promise {
let loader: Loader;
if (!noLoading) {
loader = new Loader().loading();
}
return new Promise