-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
68 changed files
with
2,285 additions
and
574 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
{ | ||
".": "0.7.0" | ||
".": "0.8.0" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
// Copyright 2024 Defense Unicorns | ||
// SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial | ||
|
||
package api | ||
|
||
import ( | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/pem" | ||
"errors" | ||
"log/slog" | ||
"math/big" | ||
"net" | ||
"net/http" | ||
"os" | ||
"os/signal" | ||
"syscall" | ||
"time" | ||
|
||
"github.com/go-chi/chi/v5" | ||
) | ||
|
||
// serveAirgap starts a server assuming airgap and uses self-signed certificates | ||
func serveAirgap(r *chi.Mux) error { | ||
err := generateCerts() | ||
if err != nil { | ||
return errors.New("failed to generate certs") | ||
} | ||
defer cleanupCerts() | ||
|
||
srv := &http.Server{ | ||
Addr: "127.0.0.1:8443", | ||
ReadHeaderTimeout: 10 * time.Second, | ||
Handler: r, | ||
} | ||
|
||
// Start server in goroutine so we can handle shutdown | ||
var serverErr error | ||
stop := make(chan os.Signal, 1) | ||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM) | ||
go func() { | ||
//nolint:gosec,govet | ||
if err := srv.ListenAndServeTLS("airgap-cert.pem", "airgap-key.pem"); err != nil && !errors.Is(err, http.ErrServerClosed) { | ||
slog.Error("Failed to start server:", "error", err) | ||
serverErr = err | ||
stop <- syscall.SIGTERM // send signal to kill stop channel | ||
return | ||
} | ||
}() | ||
<-stop | ||
slog.Info("Shutting down server") | ||
return serverErr | ||
} | ||
|
||
// isAirgapped checks if we're in an airgapped environment by attempting a DNS query against uds.dev | ||
func isAirgapped() bool { | ||
_, err := net.LookupHost("runtime-local.uds.dev") | ||
return err != nil | ||
} | ||
|
||
// generateCerts creates self-signed certificates for running locally in the airgap | ||
func generateCerts() error { | ||
// Generate private key | ||
privateKey, err := rsa.GenerateKey(rand.Reader, 2048) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Create certificate template | ||
template := x509.Certificate{ | ||
SerialNumber: big.NewInt(1), | ||
Subject: pkix.Name{ | ||
CommonName: "localhost", | ||
}, | ||
NotBefore: time.Now(), | ||
NotAfter: time.Now().Add(365 * 24 * time.Hour), // Valid for 1 year | ||
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||
ExtKeyUsage: []x509.ExtKeyUsage{ | ||
x509.ExtKeyUsageServerAuth, | ||
}, | ||
BasicConstraintsValid: true, | ||
DNSNames: []string{"localhost"}, | ||
} | ||
|
||
// Create certificate using template | ||
derBytes, err := x509.CreateCertificate( | ||
rand.Reader, | ||
&template, | ||
&template, | ||
&privateKey.PublicKey, | ||
privateKey, | ||
) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Save certificate to file | ||
certFile, err := os.Create("airgap-cert.pem") | ||
if err != nil { | ||
return err | ||
} | ||
defer certFile.Close() | ||
|
||
err = pem.Encode(certFile, &pem.Block{ | ||
Type: "CERTIFICATE", | ||
Bytes: derBytes, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Save private key to file | ||
keyFile, err := os.Create("airgap-key.pem") | ||
if err != nil { | ||
return err | ||
} | ||
defer keyFile.Close() | ||
|
||
err = pem.Encode(keyFile, &pem.Block{ | ||
Type: "RSA PRIVATE KEY", | ||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey), | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func cleanupCerts() { | ||
slog.Info("Cleaning up short-lived airgap certs") | ||
os.Remove("airgap-cert.pem") | ||
os.Remove("airgap-key.pem") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// Copyright 2024 Defense Unicorns | ||
// SPDX-License-Identifier: AGPL-3.0-or-later OR LicenseRef-Defense-Unicorns-Commercial | ||
|
||
package api | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"log/slog" | ||
"os" | ||
"syscall" | ||
"testing" | ||
"time" | ||
|
||
"github.com/go-chi/chi/v5" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestServeAirgap(t *testing.T) { | ||
// Setup test context | ||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) | ||
defer cancel() | ||
|
||
// Capture log output | ||
var buf bytes.Buffer | ||
logger := slog.New(slog.NewTextHandler(&buf, nil)) | ||
slog.SetDefault(logger) | ||
|
||
r := chi.NewRouter() | ||
|
||
// Start server in background | ||
done := make(chan error) | ||
go func() { | ||
done <- serveAirgap(r) | ||
}() | ||
|
||
// Wait for either timeout or server error | ||
select { | ||
case err := <-done: | ||
t.Fatal("Server stopped unexpectedly:", err) | ||
case <-ctx.Done(): | ||
// Send shutdown signal | ||
p, _ := os.FindProcess(os.Getpid()) | ||
p.Signal(syscall.SIGTERM) | ||
err := <-done | ||
require.NoError(t, err) | ||
} | ||
|
||
// Verify sucessful shutdown | ||
logOutput := buf.String() | ||
require.Contains(t, logOutput, "Shutting down server") | ||
require.Contains(t, logOutput, "Cleaning up") | ||
} |
Oops, something went wrong.