mirror of
https://github.com/yusing/godoxy.git
synced 2026-04-23 00:38:33 +02:00
simplify setup process with WebUI
This commit is contained in:
14
agent/pkg/agent/agent.compose.yml
Normal file
14
agent/pkg/agent/agent.compose.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
services:
|
||||
agent:
|
||||
image: "{{.Image}}"
|
||||
container_name: godoxy-agent
|
||||
restart: always
|
||||
network_mode: host # do not change this
|
||||
environment:
|
||||
AGENT_NAME: "{{.Name}}"
|
||||
AGENT_PORT: "{{.Port}}"
|
||||
AGENT_CA_CERT: "{{.CACert}}"
|
||||
AGENT_SSL_CERT: "{{.SSLCert}}"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ./compose.yml:/app/compose.yml
|
||||
@@ -38,7 +38,7 @@ const (
|
||||
EndpointLogs = "/logs"
|
||||
EndpointSystemInfo = "/system_info"
|
||||
|
||||
AgentHost = certs.CertsDNSName
|
||||
AgentHost = CertsDNSName
|
||||
|
||||
APIEndpointBase = "/godoxy/agent"
|
||||
APIBaseURL = "https://" + AgentHost + APIEndpointBase
|
||||
@@ -80,20 +80,7 @@ func checkVersion(a, b string) bool {
|
||||
return withoutBuildTime(a) == withoutBuildTime(b)
|
||||
}
|
||||
|
||||
func (cfg *AgentConfig) Start(parent task.Parent) E.Error {
|
||||
certData, err := os.ReadFile(certs.AgentCertsFilename(cfg.Addr))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return E.Errorf("agents certs not found, did you run `godoxy new-agent %s ...`?", cfg.Addr)
|
||||
}
|
||||
return E.Wrap(err)
|
||||
}
|
||||
|
||||
ca, crt, key, err := certs.ExtractCert(certData)
|
||||
if err != nil {
|
||||
return E.Wrap(err)
|
||||
}
|
||||
|
||||
func (cfg *AgentConfig) StartWithCerts(parent task.Parent, ca, crt, key []byte) E.Error {
|
||||
clientCert, err := tls.X509KeyPair(crt, key)
|
||||
if err != nil {
|
||||
return E.Wrap(err)
|
||||
@@ -109,6 +96,7 @@ func (cfg *AgentConfig) Start(parent task.Parent) E.Error {
|
||||
cfg.tlsConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{clientCert},
|
||||
RootCAs: caCertPool,
|
||||
ServerName: CertsDNSName,
|
||||
}
|
||||
|
||||
// create transport and http client
|
||||
@@ -140,6 +128,23 @@ func (cfg *AgentConfig) Start(parent task.Parent) E.Error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cfg *AgentConfig) Start(parent task.Parent) E.Error {
|
||||
certData, err := os.ReadFile(certs.AgentCertsFilename(cfg.Addr))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return E.Errorf("agents certs not found, did you run `godoxy new-agent %s ...`?", cfg.Addr)
|
||||
}
|
||||
return E.Wrap(err)
|
||||
}
|
||||
|
||||
ca, crt, key, err := certs.ExtractCert(certData)
|
||||
if err != nil {
|
||||
return E.Wrap(err)
|
||||
}
|
||||
|
||||
return cfg.StartWithCerts(parent, ca, crt, key)
|
||||
}
|
||||
|
||||
func (cfg *AgentConfig) NewHTTPClient() *http.Client {
|
||||
return &http.Client{
|
||||
Transport: cfg.Transport(),
|
||||
|
||||
123
agent/pkg/agent/docker_compose.go
Normal file
123
agent/pkg/agent/docker_compose.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
_ "embed"
|
||||
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
//go:embed agent.compose.yml
|
||||
var agentComposeYAML []byte
|
||||
var agentComposeYAMLTemplate = template.Must(template.New("agent.compose.yml").Parse(string(agentComposeYAML)))
|
||||
|
||||
const (
|
||||
DockerImageProduction = "ghcr.io/yusing/godoxy-agent:latest"
|
||||
DockerImageNightly = "yusing/godoxy-agent-nightly:latest"
|
||||
)
|
||||
|
||||
type (
|
||||
AgentComposeConfig struct {
|
||||
Image string
|
||||
Name string
|
||||
Port int
|
||||
CACert string
|
||||
SSLCert string
|
||||
}
|
||||
)
|
||||
|
||||
func (c *AgentComposeConfig) Generate() (string, error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
if err := agentComposeYAMLTemplate.Execute(buf, c); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func pemPairFromFile(path string) (*PEMPair, error) {
|
||||
cert, err := os.ReadFile(path + ".crt")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := os.ReadFile(path + ".key")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PEMPair{
|
||||
Cert: cert,
|
||||
Key: key,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func rmOldCerts(p string) error {
|
||||
files, err := utils.ListFiles(p, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, file := range files {
|
||||
if err := os.Remove(path.Join(p, file)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dockerCompose struct {
|
||||
Services struct {
|
||||
GodoxyAgent struct {
|
||||
Environment struct {
|
||||
AGENT_NAME string `yaml:"GODOXY_AGENT_NAME"`
|
||||
AGENT_PORT string `yaml:"GODOXY_AGENT_PORT"`
|
||||
} `yaml:"environment"`
|
||||
} `yaml:"godoxy-agent"`
|
||||
} `yaml:"services"`
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
func MigrateFromOld() error {
|
||||
oldCompose, err := os.ReadFile("/app/compose.yml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var compose dockerCompose
|
||||
if err := yaml.Unmarshal(oldCompose, &compose); err != nil {
|
||||
return err
|
||||
}
|
||||
ca, err := pemPairFromFile("/app/certs/ca")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
agentCert, err := pemPairFromFile("/app/certs/agent")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var composeConfig AgentComposeConfig
|
||||
composeConfig.Image = DockerImageNightly
|
||||
composeConfig.Name = compose.Services.GodoxyAgent.Environment.AGENT_NAME
|
||||
composeConfig.Port, err = strconv.Atoi(compose.Services.GodoxyAgent.Environment.AGENT_PORT) // ignore error, empty is fine
|
||||
if composeConfig.Port == 0 {
|
||||
composeConfig.Port = 8890
|
||||
}
|
||||
composeConfig.CACert = ca.String()
|
||||
composeConfig.SSLCert = agentCert.String()
|
||||
composeTemplate, err := composeConfig.Generate()
|
||||
if err != nil {
|
||||
return E.Wrap(err, "failed to generate new docker compose")
|
||||
}
|
||||
|
||||
if err := os.WriteFile("/app/compose.yml", []byte(composeTemplate), 0600); err != nil {
|
||||
return E.Wrap(err, "failed to write new docker compose")
|
||||
}
|
||||
|
||||
logging.Info().Msg("Migrated from old docker compose:")
|
||||
logging.Info().Msg(composeTemplate)
|
||||
return nil
|
||||
}
|
||||
139
agent/pkg/agent/new_agent.go
Normal file
139
agent/pkg/agent/new_agent.go
Normal file
@@ -0,0 +1,139 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
CertsDNSName = "godoxy.agent"
|
||||
KeySize = 2048
|
||||
)
|
||||
|
||||
func toPEMPair(certDER []byte, key *rsa.PrivateKey) *PEMPair {
|
||||
return &PEMPair{
|
||||
Cert: pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
|
||||
Key: pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)}),
|
||||
}
|
||||
}
|
||||
|
||||
func b64Encode(data []byte) string {
|
||||
return base64.StdEncoding.EncodeToString(data)
|
||||
}
|
||||
|
||||
func b64Decode(data string) ([]byte, error) {
|
||||
return base64.StdEncoding.DecodeString(data)
|
||||
}
|
||||
|
||||
type PEMPair struct {
|
||||
Cert, Key []byte
|
||||
}
|
||||
|
||||
func (p *PEMPair) String() string {
|
||||
return b64Encode(p.Cert) + ";" + b64Encode(p.Key)
|
||||
}
|
||||
|
||||
func (p *PEMPair) Load(data string) (err error) {
|
||||
parts := strings.Split(data, ";")
|
||||
if len(parts) != 2 {
|
||||
return errors.New("invalid PEM pair")
|
||||
}
|
||||
p.Cert, err = b64Decode(parts[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.Key, err = b64Decode(parts[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PEMPair) ToTLSCert() (*tls.Certificate, error) {
|
||||
cert, err := tls.X509KeyPair(p.Cert, p.Key)
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
func NewAgent() (ca, srv, client *PEMPair, err error) {
|
||||
// Create the CA's certificate
|
||||
caTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"GoDoxy"},
|
||||
CommonName: CertsDNSName,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, KeySize)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
caDER, err := x509.CreateCertificate(rand.Reader, caTemplate, caTemplate, &caKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
ca = toPEMPair(caDER, caKey)
|
||||
|
||||
// Generate a new private key for the server certificate
|
||||
serverKey, err := rsa.GenerateKey(rand.Reader, KeySize)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
srvTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Issuer: caTemplate.Subject,
|
||||
Subject: caTemplate.Subject,
|
||||
DNSNames: []string{CertsDNSName},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1000, 0, 0), // Add validity period
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
}
|
||||
|
||||
srvCertDER, err := x509.CreateCertificate(rand.Reader, srvTemplate, caTemplate, &serverKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
srv = toPEMPair(srvCertDER, serverKey)
|
||||
|
||||
clientKey, err := rsa.GenerateKey(rand.Reader, KeySize)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
clientTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(3),
|
||||
Issuer: caTemplate.Subject,
|
||||
Subject: caTemplate.Subject,
|
||||
DNSNames: []string{CertsDNSName},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1000, 0, 0),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
clientCertDER, err := x509.CreateCertificate(rand.Reader, clientTemplate, caTemplate, &clientKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
client = toPEMPair(clientCertDER, clientKey)
|
||||
return
|
||||
}
|
||||
91
agent/pkg/agent/new_agent_test.go
Normal file
91
agent/pkg/agent/new_agent_test.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||
)
|
||||
|
||||
func TestNewAgent(t *testing.T) {
|
||||
ca, srv, client, err := NewAgent()
|
||||
ExpectNoError(t, err)
|
||||
ExpectTrue(t, ca != nil)
|
||||
ExpectTrue(t, srv != nil)
|
||||
ExpectTrue(t, client != nil)
|
||||
}
|
||||
|
||||
func TestPEMPair(t *testing.T) {
|
||||
ca, srv, client, err := NewAgent()
|
||||
ExpectNoError(t, err)
|
||||
|
||||
for i, p := range []*PEMPair{ca, srv, client} {
|
||||
t.Run(fmt.Sprintf("load-%d", i), func(t *testing.T) {
|
||||
var pp PEMPair
|
||||
err := pp.Load(p.String())
|
||||
ExpectNoError(t, err)
|
||||
ExpectBytesEqual(t, p.Cert, pp.Cert)
|
||||
ExpectBytesEqual(t, p.Key, pp.Key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPEMPairToTLSCert(t *testing.T) {
|
||||
ca, srv, client, err := NewAgent()
|
||||
ExpectNoError(t, err)
|
||||
|
||||
for i, p := range []*PEMPair{ca, srv, client} {
|
||||
t.Run(fmt.Sprintf("toTLSCert-%d", i), func(t *testing.T) {
|
||||
cert, err := p.ToTLSCert()
|
||||
ExpectNoError(t, err)
|
||||
ExpectTrue(t, cert != nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerClient(t *testing.T) {
|
||||
ca, srv, client, err := NewAgent()
|
||||
ExpectNoError(t, err)
|
||||
|
||||
srvTLS, err := srv.ToTLSCert()
|
||||
ExpectNoError(t, err)
|
||||
ExpectTrue(t, srvTLS != nil)
|
||||
|
||||
clientTLS, err := client.ToTLSCert()
|
||||
ExpectNoError(t, err)
|
||||
ExpectTrue(t, clientTLS != nil)
|
||||
|
||||
caPool := x509.NewCertPool()
|
||||
ExpectTrue(t, caPool.AppendCertsFromPEM(ca.Cert))
|
||||
|
||||
srvTLSConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{*srvTLS},
|
||||
ClientCAs: caPool,
|
||||
ClientAuth: tls.RequireAndVerifyClientCert,
|
||||
}
|
||||
|
||||
clientTLSConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{*clientTLS},
|
||||
RootCAs: caPool,
|
||||
ServerName: CertsDNSName,
|
||||
}
|
||||
|
||||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
server.TLS = srvTLSConfig
|
||||
server.StartTLS()
|
||||
defer server.Close()
|
||||
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: clientTLSConfig},
|
||||
}
|
||||
|
||||
resp, err := httpClient.Get(server.URL)
|
||||
ExpectNoError(t, err)
|
||||
ExpectEqual(t, resp.StatusCode, http.StatusOK)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func MachineIP() (string, bool) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
interfaces = []net.Interface{}
|
||||
}
|
||||
for _, in := range interfaces {
|
||||
addrs, err := in.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if !strings.HasPrefix(in.Name, "eth") && !strings.HasPrefix(in.Name, "en") {
|
||||
continue
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
|
||||
if ipnet.IP.To4() != nil {
|
||||
return ipnet.IP.String(), true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
package certs
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"math/big"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
CertsDNSName = "godoxy.agent"
|
||||
|
||||
caCertPath = "certs/ca.crt"
|
||||
caKeyPath = "certs/ca.key"
|
||||
srvCertPath = "certs/agent.crt"
|
||||
srvKeyPath = "certs/agent.key"
|
||||
)
|
||||
|
||||
func loadCerts(certPath, keyPath string) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
||||
return &cert, err
|
||||
}
|
||||
|
||||
func write(b []byte, f *os.File) error {
|
||||
_, err := f.Write(b)
|
||||
return err
|
||||
}
|
||||
|
||||
func saveCerts(certDER []byte, key *rsa.PrivateKey, certPath, keyPath string) ([]byte, []byte, error) {
|
||||
certPEM, keyPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}),
|
||||
pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(key)})
|
||||
|
||||
if certPath == "" || keyPath == "" {
|
||||
return certPEM, keyPEM, nil
|
||||
}
|
||||
|
||||
certFile, err := os.Create(certPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer certFile.Close()
|
||||
|
||||
keyFile, err := os.Create(keyPath)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
defer keyFile.Close()
|
||||
|
||||
return certPEM, keyPEM, errors.Join(
|
||||
write(certPEM, certFile),
|
||||
write(keyPEM, keyFile),
|
||||
)
|
||||
}
|
||||
|
||||
func checkExists(certPath, keyPath string) bool {
|
||||
certExists, err := utils.FileExists(certPath)
|
||||
if err != nil {
|
||||
E.LogFatal("cert error", err)
|
||||
}
|
||||
keyExists, err := utils.FileExists(keyPath)
|
||||
if err != nil {
|
||||
E.LogFatal("key error", err)
|
||||
}
|
||||
return certExists && keyExists
|
||||
}
|
||||
|
||||
func InitCerts() (ca *tls.Certificate, srv *tls.Certificate, isNew bool, err error) {
|
||||
if checkExists(caCertPath, caKeyPath) && checkExists(srvCertPath, srvKeyPath) {
|
||||
logging.Info().Msg("Loading existing certs...")
|
||||
ca, err = loadCerts(caCertPath, caKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
srv, err = loadCerts(srvCertPath, srvKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
logging.Info().Msg("Verifying agent cert...")
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
roots.AddCert(ca.Leaf)
|
||||
|
||||
srvCert, err := x509.ParseCertificate(srv.Certificate[0])
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
// check if srv is signed by ca
|
||||
if _, err := srvCert.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
}); err == nil {
|
||||
logging.Info().Msg("OK")
|
||||
return ca, srv, false, nil
|
||||
}
|
||||
logging.Error().Msg("Agent cert and CA cert mismatch, regenerating")
|
||||
}
|
||||
|
||||
// Create the CA's certificate
|
||||
caTemplate := x509.Certificate{
|
||||
SerialNumber: big.NewInt(1),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"GoDoxy"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
|
||||
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
|
||||
BasicConstraintsValid: true,
|
||||
IsCA: true,
|
||||
}
|
||||
|
||||
caKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
caCertDER, err := x509.CreateCertificate(rand.Reader, &caTemplate, &caTemplate, &caKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
certPEM, keyPEM, err := saveCerts(caCertDER, caKey, caCertPath, caKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
caCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
ca = &caCert
|
||||
|
||||
// Generate a new private key for the server certificate
|
||||
serverKey, err := rsa.GenerateKey(rand.Reader, 4096)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
srvTemplate := caTemplate
|
||||
srvTemplate.Issuer = srvTemplate.Subject
|
||||
srvTemplate.DNSNames = append(srvTemplate.DNSNames, CertsDNSName)
|
||||
|
||||
srvCertDER, err := x509.CreateCertificate(rand.Reader, &srvTemplate, &caTemplate, &serverKey.PublicKey, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
certPEM, keyPEM, err = saveCerts(srvCertDER, serverKey, srvCertPath, srvKeyPath)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
agentCert, err := tls.X509KeyPair(certPEM, keyPEM)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
srv = &agentCert
|
||||
|
||||
return ca, srv, true, nil
|
||||
}
|
||||
|
||||
func NewClientCert(ca *tls.Certificate) ([]byte, []byte, error) {
|
||||
// Generate the SSL's private key
|
||||
sslKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Create the SSL's certificate
|
||||
sslTemplate := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(2),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"GoDoxy"},
|
||||
CommonName: CertsDNSName,
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(1000, 0, 0), // 1000 years
|
||||
KeyUsage: x509.KeyUsageDigitalSignature,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
|
||||
}
|
||||
|
||||
// Sign the certificate with the CA
|
||||
sslCertDER, err := x509.CreateCertificate(rand.Reader, sslTemplate, ca.Leaf, &sslKey.PublicKey, ca.PrivateKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return saveCerts(sslCertDER, sslKey, "", "")
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func writeFile(zipWriter *zip.Writer, name string, data []byte) error {
|
||||
w, err := zipWriter.CreateHeader(&zip.FileHeader{
|
||||
Name: name,
|
||||
Method: zip.Deflate,
|
||||
Method: zip.Store,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -31,8 +31,7 @@ func readFile(f *zip.File) ([]byte, error) {
|
||||
}
|
||||
|
||||
func ZipCert(ca, crt, key []byte) ([]byte, error) {
|
||||
data := bytes.NewBuffer(nil)
|
||||
data.Grow(6144)
|
||||
data := bytes.NewBuffer(make([]byte, 0, 6144))
|
||||
zipWriter := zip.NewWriter(data)
|
||||
defer zipWriter.Close()
|
||||
|
||||
|
||||
56
agent/pkg/env/env.go
vendored
56
agent/pkg/env/env.go
vendored
@@ -1,11 +1,7 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/yusing/go-proxy/internal/common"
|
||||
)
|
||||
@@ -24,54 +20,6 @@ var (
|
||||
AgentRegistrationPort = common.GetEnvInt("AGENT_REGISTRATION_PORT", 8891)
|
||||
AgentSkipClientCertCheck = common.GetEnvBool("AGENT_SKIP_CLIENT_CERT_CHECK", false)
|
||||
|
||||
RegistrationAllowedHosts = common.GetCommaSepEnv("REGISTRATION_ALLOWED_HOSTS", "")
|
||||
RegistrationAllowedCIDRs []*net.IPNet
|
||||
AgentCACert = common.GetEnvString("AGENT_CA_CERT", "")
|
||||
AgentSSLCert = common.GetEnvString("AGENT_SSL_CERT", "")
|
||||
)
|
||||
|
||||
func init() {
|
||||
cidrs, err := toCIDRs(RegistrationAllowedHosts)
|
||||
if err != nil {
|
||||
log.Fatalf("failed to parse allowed hosts: %v", err)
|
||||
}
|
||||
RegistrationAllowedCIDRs = cidrs
|
||||
}
|
||||
|
||||
func toCIDRs(hosts []string) ([]*net.IPNet, error) {
|
||||
cidrs := make([]*net.IPNet, 0, len(hosts))
|
||||
for _, host := range hosts {
|
||||
if !strings.Contains(host, "/") {
|
||||
host += "/32"
|
||||
}
|
||||
_, cidr, err := net.ParseCIDR(host)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cidrs = append(cidrs, cidr)
|
||||
}
|
||||
return cidrs, nil
|
||||
}
|
||||
|
||||
var warnOnce sync.Once
|
||||
|
||||
func IsAllowedHost(remoteAddr string) bool {
|
||||
if len(RegistrationAllowedCIDRs) == 0 {
|
||||
warnOnce.Do(func() {
|
||||
log.Println("Warning: REGISTRATION_ALLOWED_HOSTS is empty, allowing all hosts")
|
||||
})
|
||||
return true
|
||||
}
|
||||
ip, _, err := net.SplitHostPort(remoteAddr)
|
||||
if err != nil {
|
||||
ip = remoteAddr
|
||||
}
|
||||
netIP := net.ParseIP(ip)
|
||||
if netIP == nil {
|
||||
return false
|
||||
}
|
||||
for _, cidr := range RegistrationAllowedCIDRs {
|
||||
if cidr.Contains(netIP) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/yusing/go-proxy/agent/pkg/agent"
|
||||
"github.com/yusing/go-proxy/agent/pkg/certs"
|
||||
"github.com/yusing/go-proxy/agent/pkg/env"
|
||||
v1 "github.com/yusing/go-proxy/internal/api/v1"
|
||||
"github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||
E "github.com/yusing/go-proxy/internal/error"
|
||||
"github.com/yusing/go-proxy/internal/logging"
|
||||
"github.com/yusing/go-proxy/internal/logging/memlogger"
|
||||
"github.com/yusing/go-proxy/internal/metrics/systeminfo"
|
||||
"github.com/yusing/go-proxy/internal/task"
|
||||
"github.com/yusing/go-proxy/internal/utils/strutils"
|
||||
)
|
||||
|
||||
@@ -54,46 +47,3 @@ func NewAgentHandler() http.Handler {
|
||||
mux.ServeMux.HandleFunc("/", DockerSocketHandler())
|
||||
return mux
|
||||
}
|
||||
|
||||
// NewRegistrationHandler creates a new registration handler
|
||||
// It checks if the request is coming from an allowed host
|
||||
// Generates a new client certificate and zips it
|
||||
// Sends the zipped certificate to the client
|
||||
// its run only once on agent first start.
|
||||
func NewRegistrationHandler(task *task.Task, ca *tls.Certificate) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !env.IsAllowedHost(r.RemoteAddr) {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if r.URL.Path == "/done" {
|
||||
logging.Info().Msg("registration done")
|
||||
task.Finish(nil)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
logging.Info().Msgf("received registration request from %s", r.RemoteAddr)
|
||||
|
||||
caPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: ca.Certificate[0]})
|
||||
|
||||
crt, key, err := certs.NewClientCert(ca)
|
||||
if err != nil {
|
||||
utils.HandleErr(w, r, E.Wrap(err, "failed to generate client certificate"))
|
||||
return
|
||||
}
|
||||
|
||||
zipped, err := certs.ZipCert(caPEM, crt, key)
|
||||
if err != nil {
|
||||
utils.HandleErr(w, r, E.Wrap(err, "failed to zip certificate"))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
if _, err := w.Write(zipped); err != nil {
|
||||
logging.Error().Err(err).Msg("failed to respond to registration request")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,32 +77,3 @@ func StartAgentServer(parent task.Parent, opt Options) {
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func StartRegistrationServer(parent task.Parent, opt Options) {
|
||||
t := parent.Subtask("registration_server")
|
||||
|
||||
logger := logging.GetLogger()
|
||||
registrationServer := &http.Server{
|
||||
Addr: fmt.Sprintf(":%d", opt.Port),
|
||||
Handler: handler.NewRegistrationHandler(t, opt.CACert),
|
||||
ErrorLog: log.New(logger, "", 0),
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := registrationServer.ListenAndServe()
|
||||
server.HandleError(logger, err, "failed to serve registration server")
|
||||
}()
|
||||
|
||||
logging.Info().Int("port", opt.Port).Msg("registration server started")
|
||||
|
||||
defer t.Finish(nil)
|
||||
<-t.Context().Done()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := registrationServer.Shutdown(ctx)
|
||||
server.HandleError(logger, err, "failed to shutdown registration server")
|
||||
|
||||
logging.Info().Int("port", opt.Port).Msg("registration server stopped")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user