Skip to content

Commit

Permalink
Extract a "challenge" WebApp
Browse files Browse the repository at this point in the history
  • Loading branch information
Gene Gleyzer committed Aug 23, 2024
1 parent b56c3d2 commit bd424ee
Show file tree
Hide file tree
Showing 12 changed files with 228 additions and 118 deletions.
24 changes: 0 additions & 24 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,3 @@ tasks.register("clean") {
description = "Delete previous build results"
delete(libDir)
}

val build = tasks.register("build") {
group = "Build"
description = "Build all"

dependsOn(project(":kernel") .tasks["build"])
dependsOn(project(":host") .tasks["build"])
dependsOn(project(":platformDB") .tasks["build"])
dependsOn(project(":platformUI") .tasks["build"])
dependsOn(project(":platformCLI").tasks["build"])
dependsOn(project(":stub") .tasks["build"])
}

tasks.register("run") {
group = "Run"
description = "Run the platform"

dependsOn(build)

doLast {
println("Please run the platform directly using the following command:")
println(" xec -L lib/ kernel.xtc [password]")
}
}
4 changes: 3 additions & 1 deletion common/src/main/x/common.x
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ module common.xqiz.it {

import ecstasy.text.SimpleLog;

typedef function void (String) as Reporting;

/**
* A Log as a service.
*/
service ErrorLog
extends SimpleLog {

void reportAll(function void (String) report) {
void reportAll(Reporting report) {
for (String msg : messages) {
report(msg);
}
Expand Down
43 changes: 43 additions & 0 deletions common/src/main/x/common/ProxyManager.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import crypto.CryptoPassword;
import crypto.KeyStore;


/**
* The proxy management API.
*/
interface ProxyManager {

/**
* Send the proxy config updates updates to all proxies.
*
* @param keystore the keystore
* @param pwd the password for the keystore
* @param keyName the key name for the key/certificate pair
* @param hostName the host name to be updated
* @param report the function to report errors to
*/
void updateProxyConfig(KeyStore keystore, CryptoPassword pwd,
String keyName, String hostName, Reporting report);

/**
* Notify all proxies that a config needs to be removed.
*
* @param hostName the host name to be removed
* @param report the function to report errors to
*/
void removeProxyConfig(String hostName, Reporting report);

/**
* Trivial "do nothing" implementation.
*/
static ProxyManager NoProxies = new ProxyManager() {
construct() {} finally { makeImmutable(); }

@Override
void updateProxyConfig(KeyStore keystore, CryptoPassword pwd,
String keyName, String hostName, Reporting report) {}

@Override
void removeProxyConfig(String hostName, Reporting report) {}
};
}
4 changes: 2 additions & 2 deletions host/src/main/x/host.x
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ module host.xqiz.it {
* Bootstrapping: configure and return the HostManager.
*/
common.HostManager configure(xenia.HttpServer httpServer, Directory accountsDir,
net.Uri[] receivers) {
HostManager mgr = new HostManager(httpServer, accountsDir, receivers);
common.ProxyManager proxyManager) {
HostManager mgr = new HostManager(httpServer, accountsDir, proxyManager);
return &mgr.maskAs(common.HostManager);
}
}
89 changes: 9 additions & 80 deletions host/src/main/x/host/HostManager.x
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import ecstasy.text.Log;
import common.AccountManager;
import common.AppHost;
import common.DbHost;
import common.ProxyManager;
import common.WebHost;

import common.model.DbAppInfo;
Expand All @@ -17,17 +18,13 @@ import common.model.WebAppInfo;
import common.names;
import common.utils;

import convert.formats.Base64Format;

import crypto.Certificate;
import crypto.CertificateManager;
import crypto.CryptoPassword;
import crypto.KeyStore;

import net.Uri;

import web.Client;
import web.HttpClient;
import web.ResponseIn;
import web.WebApp;
import web.WebService;
Expand All @@ -40,7 +37,7 @@ import xenia.HttpServer;
/**
* The module for basic hosting functionality.
*/
service HostManager(HttpServer httpServer, Directory accountsDir, Uri[] receivers)
service HostManager(HttpServer httpServer, Directory accountsDir, ProxyManager proxyManager)
implements common.HostManager {

@Inject Clock clock;
Expand All @@ -56,25 +53,15 @@ service HostManager(HttpServer httpServer, Directory accountsDir, Uri[] receiver
private Directory accountsDir;

/**
* The receivers associated with proxy servers.
* The proxy manager.
*/
private Uri[] receivers;
private ProxyManager proxyManager;

/**
* Deployed AppHosts keyed by the deployment name.
*/
private Map<String, AppHost> deployedHosts = new HashMap();

/**
* The client used to talk to external services.
*/
@Lazy Client client.calc() = new HttpClient();

/**
* The timeout duration for receivers' updates.
*/
static Duration receiverTimeout = Duration.ofSeconds(5);

/**
* The key store name to use.
*/
Expand Down Expand Up @@ -168,10 +155,10 @@ service HostManager(HttpServer httpServer, Directory accountsDir, Uri[] receiver

@Inject(opts=new KeyStore.Info(store.contents, pwd)) KeyStore keystore;
assert Certificate cert := keystore.getCertificate(hostName), cert.valid;
updateProxyConfig(hostName, homeDir, keystore, pwd);

errors.add($|Info: {newStore ? "created" : "renewed"} a certificate for "{hostName}"
);
log(homeDir, $|{newStore ? "Created" : "Renewed"} a certificate for "{hostName}"
);
proxyManager.updateProxyConfig^(keystore, pwd, hostName, hostName, &log(homeDir));
return True, cert;
} catch (Exception e) {
try {
Expand All @@ -190,51 +177,6 @@ service HostManager(HttpServer httpServer, Directory accountsDir, Uri[] receiver
}
}

/**
* For every proxy, there might be a receiver associated with it. Send the corresponding
* updates.
*/
private void updateProxyConfig(String hostName, Directory homeDir,
KeyStore keystore, CryptoPassword pwd) {
@Inject CertificateManager manager;

for (Uri receiverUri : receivers) {
Byte[] bytes = manager.extractKey(keystore, pwd, hostName);
String pemKey = $|-----BEGIN PRIVATE KEY-----
|{Base64Format.Instance.encode(bytes, pad=True, lineLength=64)}
|-----END PRIVATE KEY-----
|
;

Boolean success;
try (val t = new Timeout(receiverTimeout)) {
ResponseIn response = client.put(
receiverUri.with(path=$"/nginx/{hostName}/key"), pemKey, Text);

if (response.status == OK) {
StringBuffer pemCert = new StringBuffer();
for (Certificate cert : keystore.getCertificateChain(hostName)) {
pemCert.append(
$|-----BEGIN CERTIFICATE-----
|{Base64Format.Instance.encode(cert.toDerBytes(), pad=True, lineLength=64)}
|-----END CERTIFICATE-----
|
);
}
response = client.put(
receiverUri.with(path=$"/nginx/{hostName}/cert"), pemCert.toString(), Text);
}
success = response.status == OK;
} catch (Exception e) {
success = False;
}

if (!success) {
log(homeDir, $"Failed to update the proxy server at {receiverUri}");
}
}
}

@Override
void addStubRoute(String accountName, WebAppInfo appInfo, CryptoPassword? pwd = Null) {
Directory homeDir = ensureDeploymentHomeDirectory(accountName, appInfo.deployment);
Expand Down Expand Up @@ -359,22 +301,9 @@ service HostManager(HttpServer httpServer, Directory accountsDir, Uri[] receiver

Boolean keepLogs = True; // TODO soft code?

// notify the receivers
for (Uri receiverUri : receivers) {
using (new Timeout(receiverTimeout)) {
ResponseIn response = client.delete^(receiverUri.with(path=$"/nginx/{hostName}"));
if (keepLogs) {
&response.whenComplete((r, e) -> {
if (e != Null) {
log(homeDir, $|Failed to remove "{hostName}" route from the proxy \
|server "{receiverUri}"
);
}
});
}
}
}
removeFiles(homeDir, keepLogs);

proxyManager.removeProxyConfig^(hostName, keepLogs ? &log(homeDir) : (_) -> {});
}


Expand Down
37 changes: 31 additions & 6 deletions kernel/src/main/x/kernel.x
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module kernel.xqiz.it {

import common.ErrorLog;
import common.HostManager;
import common.ProxyManager;

import common.names;
import common.utils;
Expand Down Expand Up @@ -181,6 +182,35 @@ module kernel.xqiz.it {

@Inject(resourceName="server") HttpServer httpServer;

// load the proxy manager (pretend it may be missing)
ProxyManager proxyManager;
if (proxies.empty) {
proxyManager = NoProxies;
} else {
if (ModuleTemplate proxyModule := repository.getModule("proxy_manager.xqiz.it")) {
proxyModule = proxyModule.parent.resolve(repository).mainModule;
if (Container container :=
utils.createContainer(repository, proxyModule, hostDir, buildDir, True, [],
(_) -> False, errors)) {
// TODO: we should either soft-code the receiver's protocol and port or
// have the configuration supply the receivers' URI, from which we would
// compute the proxy addresses
Uri[] receivers = new Uri[proxies.size]
(i -> new Uri(scheme="https", ip=proxies[i], port=8091)).
freeze(inPlace=True);
proxyManager = container.invoke("configure",
Tuple:(receivers))[0]. as(ProxyManager);
} else {
return;
}
} else {
proxyManager = NoProxies;
console.print(\|Warning: Failed to load the ProxyManager; new deployment \
|configurations *will not be* propagated to proxy servers
);
}
}

// create a container for the host manager and configure it
console.print("Info: Starting the HostManager...");

Expand All @@ -189,13 +219,8 @@ module kernel.xqiz.it {
if (Container container :=
utils.createContainer(repository, hostModule, hostDir, buildDir, True, [],
(_) -> False, errors)) {
// TODO: we should either soft-code the receiver's protocol and port or
// have the configuration supply the receivers' URI, from which we would
// compute the proxy addresses
Uri[] receivers = new Uri[proxies.size]
(i -> new Uri(scheme="https", ip=proxies[i], port=8091)).freeze(inPlace=True);
hostManager = container.invoke("configure",
Tuple:(httpServer, accountsDir, receivers))[0]. as(HostManager);
Tuple:(httpServer, accountsDir, proxyManager))[0]. as(HostManager);
} else {
return;
}
Expand Down
6 changes: 3 additions & 3 deletions platformUI/src/main/x/platformUI.x
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,17 @@ module platformUI.xqiz.it {
// create WebHosts for all `autoStart` web applications
for (AppInfo appInfo : accountInfo.apps.values) {
if (appInfo.is(WebAppInfo)) {
CryptoPassword appPwd = accountManager.decrypt(appInfo.password);
if (appInfo.autoStart) {
if (hostManager.createWebHost(accountName, appInfo,
accountManager.decrypt(appInfo.password), errors)) {
if (hostManager.createWebHost(accountName, appInfo, appPwd, errors)) {
reportInitialized(appInfo, "Web");
continue;
}
accountManager.addOrUpdateApp(accountName, appInfo.with(autoStart=False));
reportFailedInitialization(appInfo, "Web", errors);
}
// set up the stub in either case
hostManager.addStubRoute(accountName, appInfo);
hostManager.addStubRoute(accountName, appInfo, appPwd);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion platformUI/src/main/x/platformUI/WebAppEndpoint.x
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ service WebAppEndpoint
return "[unknown]";
}

Directory homeDir = hostManager.ensureDeploymentHomeDirectory(accountName, appInfo.deployment);
Directory homeDir = hostManager.ensureDeploymentHomeDirectory(accountName, deployment);
if (dbName != "") {
homeDir = homeDir.dirFor(dbName);
}
Expand Down
17 changes: 17 additions & 0 deletions proxy/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Build the "proxy manager" module.
*/

tasks.register("build") {
val libDir = "${rootProject.projectDir}/lib"
val srcModule = "${projectDir}/src/main/x/proxy.x"

dependsOn(project(":common").tasks["build"])

project.exec {
commandLine("xcc",
"-o", libDir,
"-L", libDir,
srcModule)
}
}
19 changes: 19 additions & 0 deletions proxy/src/main/x/proxy.x
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* The proxy manager service. Eventually, this module should be moved to the
*/
module proxy_manager.xqiz.it {
package common import common.xqiz.it;

package convert import convert.xtclang.org;
package crypto import crypto.xtclang.org;
package net import net.xtclang.org;
package web import web.xtclang.org;

/**
* Bootstrapping: configure and return the ProxyManager.
*/
common.ProxyManager configure(net.Uri[] receivers) {
ProxyManager mgr = new ProxyManager(receivers);
return &mgr.maskAs(common.ProxyManager);
}
}
Loading

0 comments on commit bd424ee

Please sign in to comment.