Compare commits

...

6 commits

Author SHA1 Message Date
Jett Chen
59ddc36f2c Add support for ini parsing 2024-05-27 13:58:11 +08:00
Jett Chen
3ba0004370 Add button for sending toml 2024-05-19 00:23:34 +08:00
Jett Chen
c8a21c73a8 updated packages 2024-05-18 22:52:52 +08:00
Jett Chen
f6241e90d5 WIP: UI for adding config 2024-05-12 01:07:05 +08:00
Jett Chen
dd3f5d0d92 Add RPC endpoint for adding toml configs 2024-05-12 00:14:31 +08:00
Jett Chen
ae8ea8ae54 Add toml serde for wireguard config 2024-05-12 00:08:20 +08:00
21 changed files with 507 additions and 49 deletions

4
.gitignore vendored
View file

@ -6,3 +6,7 @@ target/
.DS_STORE .DS_STORE
.idea/ .idea/
burrow.sock
burrow.db
tmp/

View file

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.developer.associated-domains</key> <key>com.apple.developer.associated-domains</key>
<array> <array>
<string>applinks:burrow.rs?mode=developer</string> <string>applinks:burrow.rs?mode=developer</string>

View file

@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.developer.associated-domains</key> <key>com.apple.developer.associated-domains</key>
<array> <array>
<string>applinks:burrow.rs?mode=developer</string> <string>applinks:burrow.rs?mode=developer</string>
@ -13,7 +15,7 @@
</array> </array>
<key>com.apple.security.application-groups</key> <key>com.apple.security.application-groups</key>
<array> <array>
<string>$(APP_GROUP_IDENTIFIER)</string> <string>group.com.hackclub.burrow</string>
</array> </array>
</dict> </dict>
</plist> </plist>

View file

@ -1,10 +1,13 @@
import AuthenticationServices import AuthenticationServices
import SwiftUI import SwiftUI
import BurrowShared
#if !os(macOS) #if !os(macOS)
struct BurrowView: View { struct BurrowView: View {
@Environment(\.webAuthenticationSession) @Environment(\.webAuthenticationSession)
private var webAuthenticationSession private var webAuthenticationSession
@State private var rpcClient: Client?
@State private var showAlert = false
var body: some View { var body: some View {
NavigationStack { NavigationStack {
@ -17,6 +20,7 @@ struct BurrowView: View {
Menu { Menu {
Button("Hack Club", action: addHackClubNetwork) Button("Hack Club", action: addHackClubNetwork)
Button("WireGuard", action: addWireGuardNetwork) Button("WireGuard", action: addWireGuardNetwork)
Button("Custom", action: sncAddCustomnetwork)
} label: { } label: {
Image(systemName: "plus.circle.fill") Image(systemName: "plus.circle.fill")
.font(.title) .font(.title)
@ -42,7 +46,31 @@ struct BurrowView: View {
} }
private func addWireGuardNetwork() { private func addWireGuardNetwork() {
}
private func getClient() throws -> Client {
if self.rpcClient == nil {
let client = try Client()
self.rpcClient = client
}
return self.rpcClient!
}
private func sncAddCustomnetwork() {
Task {
try await addCustomnetwork()
}
}
private func addCustomnetwork() async {
do {
let networkToml = ""
let client = try getClient()
try await client.single_request("AddConfigToml", params: networkToml, type: BurrowResult<AnyResponseData>.self)
alert("Successs!", isPresented: $showAlert){
Button("OK", role: .cancel) {}
}
} catch {
}
} }
private func authenticateWithSlack() async throws { private func authenticateWithSlack() async throws {

View file

@ -6,30 +6,66 @@
// //
import SwiftUI import SwiftUI
import BurrowShared
struct MenuItemToggleView: View { struct MenuItemToggleView: View {
@Environment(\.tunnel) @Environment(\.tunnel)
var tunnel: Tunnel var tunnel: Tunnel
@State private var showAlert = false
var body: some View { var body: some View {
HStack { VStack {
VStack(alignment: .leading) { HStack {
Text("Burrow") VStack(alignment: .leading) {
.font(.headline) Text("Burrow")
Text(tunnel.status.description) .font(.headline)
.font(.subheadline) Text(tunnel.status.description)
} .font(.subheadline)
Spacer() }
Toggle(isOn: tunnel.toggleIsOn) { Spacer()
} Toggle(isOn: tunnel.toggleIsOn) {
}
.disabled(tunnel.toggleDisabled) .disabled(tunnel.toggleDisabled)
.toggleStyle(.switch) .toggleStyle(.switch)
}
Button("Add Custom WG Config", action: sncAddCustomnetwork)
} }
.accessibilityElement(children: .combine) .accessibilityElement(children: .combine)
.padding(.horizontal, 4) .padding(.horizontal, 4)
.padding(10) .padding(10)
.frame(minWidth: 300, minHeight: 32, maxHeight: 32) .frame(minWidth: 300, minHeight: 32, maxHeight: 32)
} }
func sncAddCustomnetwork(){
Task {
try await addCustomnetwork()
}
}
func addCustomnetwork() async {
do{
let networkToml = """
[[peers]]
public_key = "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM="
preshared_key = "ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698="
allowed_ips = ["8.8.8.8/32", "0.0.0.0/0"]
endpoint = "wg.burrow.rs:51820"
[interface]
private_key = "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8="
address = ["10.13.13.2/24"]
listen_port = 51820
dns = []
"""
let client = try Client()
try await client.single_request("AddConfigToml", params: networkToml, type: BurrowResult<AnyResponseData>.self)
alert("Successs!", isPresented: $showAlert){
Button("OK", role: .cancel) {}
}
} catch {
}
}
} }
extension Tunnel { extension Tunnel {

View file

@ -0,0 +1,14 @@
//
// RpcOperations.swift
// App
//
// Created by Jett Chen on 2024/5/18.
//
import Foundation
class RpcOperations{
func uploadConfig(){
}
}

View file

@ -11,6 +11,7 @@
0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; }; 0BA6D73C2BA6393200BD4B55 /* NWConnection+Async.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117302B2FFFC900D87C25 /* NWConnection+Async.swift */; };
0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; }; 0BA6D73D2BA6393B00BD4B55 /* NewlineProtocolFramer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00117322B3001A400D87C25 /* NewlineProtocolFramer.swift */; };
0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; }; 0BA6D73E2BA6394B00BD4B55 /* DataTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B28F1552ABF463A000D44B0 /* DataTypes.swift */; };
0BE456612BF90752005E4D47 /* RpcOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE456602BF90752005E4D47 /* RpcOperations.swift */; };
43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; }; 43AA26D82A10004900F14CE6 /* MenuItemToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */; };
D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; }; D000363D2BB8928E00E582EC /* NetworkCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */; };
D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.swift */; }; D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */ = {isa = PBXBuildFile; fileRef = D000363E2BB895FB00E582EC /* OAuth2.swift */; };
@ -79,6 +80,7 @@
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = "<group>"; }; 0B28F1552ABF463A000D44B0 /* DataTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataTypes.swift; sourceTree = "<group>"; };
0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; }; 0B46E8DF2AC918CA00BA2A3C /* Client.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Client.swift; sourceTree = "<group>"; };
0BE456602BF90752005E4D47 /* RpcOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RpcOperations.swift; sourceTree = "<group>"; };
43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = "<group>"; }; 43AA26D72A10004900F14CE6 /* MenuItemToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemToggleView.swift; sourceTree = "<group>"; };
D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = "<group>"; }; D000363C2BB8928E00E582EC /* NetworkCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkCarouselView.swift; sourceTree = "<group>"; };
D000363E2BB895FB00E582EC /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = "<group>"; }; D000363E2BB895FB00E582EC /* OAuth2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OAuth2.swift; sourceTree = "<group>"; };
@ -259,6 +261,7 @@
D0FAB5952B818B2900F6A84B /* TunnelButton.swift */, D0FAB5952B818B2900F6A84B /* TunnelButton.swift */,
D0B98FC629FDC5B5004E7149 /* Tunnel.swift */, D0B98FC629FDC5B5004E7149 /* Tunnel.swift */,
D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */, D0FAB5912B818A5900F6A84B /* NetworkExtensionTunnel.swift */,
0BE456602BF90752005E4D47 /* RpcOperations.swift */,
D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */, D0BCC5FC2A086D4700AD070D /* NetworkExtension+Async.swift */,
D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */, D05EF8C72B81818D0017AB4F /* FloatingButtonStyle.swift */,
D05B9F7929E39EED008CB1F9 /* Assets.xcassets */, D05B9F7929E39EED008CB1F9 /* Assets.xcassets */,
@ -485,6 +488,7 @@
D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */, D000363F2BB895FB00E582EC /* OAuth2.swift in Sources */,
D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */, D0FAB5962B818B2900F6A84B /* TunnelButton.swift in Sources */,
D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */, D00AA8972A4669BC005C8102 /* AppDelegate.swift in Sources */,
0BE456612BF90752005E4D47 /* RpcOperations.swift in Sources */,
D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */, D05EF8C82B81818D0017AB4F /* FloatingButtonStyle.swift in Sources */,
D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */, D032E6522B8A79C20006B8AD /* HackClub.swift in Sources */,
D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */, D05B9F7629E39EEC008CB1F9 /* BurrowApp.swift in Sources */,

View file

@ -23,8 +23,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git", "location" : "https://github.com/jpsim/SourceKitten.git",
"state" : { "state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56", "revision" : "fd4df99170f5e9d7cf9aa8312aa8506e0e7a44e7",
"version" : "0.34.1" "version" : "0.35.0"
} }
}, },
{ {
@ -32,8 +32,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git", "location" : "https://github.com/apple/swift-argument-parser.git",
"state" : { "state" : {
"revision" : "8f4d2753f0e4778c76d5f05ad16c74f707390531", "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
"version" : "1.2.3" "version" : "1.2.2"
} }
}, },
{ {
@ -41,8 +41,8 @@
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git", "location" : "https://github.com/apple/swift-syntax.git",
"state" : { "state" : {
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", "revision" : "303e5c5c36d6a558407d364878df131c3546fad8",
"version" : "509.1.1" "version" : "510.0.2"
} }
}, },
{ {
@ -51,7 +51,7 @@
"location" : "https://github.com/realm/SwiftLint.git", "location" : "https://github.com/realm/SwiftLint.git",
"state" : { "state" : {
"branch" : "main", "branch" : "main",
"revision" : "7595ad3fafc1a31086dc40ba01fd898bf6b42d5f" "revision" : "b42f6ffe77159aed1060bf607212a0410c7623b8"
} }
}, },
{ {

View file

@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>com.apple.security.network.server</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.developer.networking.networkextension</key> <key>com.apple.developer.networking.networkextension</key>
<array> <array>
<string>packet-tunnel-provider</string> <string>packet-tunnel-provider</string>

View file

@ -41,7 +41,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
override func stopTunnel(with reason: NEProviderStopReason) async { override func stopTunnel(with reason: NEProviderStopReason) async {
do { do {
let client = try Client() let client = try Client()
_ = try await client.single_request("Stop", type: BurrowResult<AnyResponseData>.self) _ = try await client.single_request("Stop", params:nil, type: BurrowResult<AnyResponseData>.self)
self.logger.log("Stopped client.") self.logger.log("Stopped client.")
} catch { } catch {
self.logger.error("Failed to stop tunnel: \(error)") self.logger.error("Failed to stop tunnel: \(error)")
@ -51,7 +51,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
guard let client = self.client else { guard let client = self.client else {
throw BurrowError.noClient throw BurrowError.noClient
} }
let srvConfig = try await client.single_request("ServerConfig", type: BurrowResult<ServerConfig>.self) let srvConfig = try await client.single_request("ServerConfig", params: nil, type: BurrowResult<ServerConfig>.self)
guard let serverconfig = srvConfig.Ok else { guard let serverconfig = srvConfig.Ok else {
throw BurrowError.resultIsError throw BurrowError.resultIsError
} }

View file

@ -81,10 +81,11 @@ public final class Client {
) )
return try await send(req) return try await send(req)
} }
public func single_request<U: Decodable>(_ request: String, type: U.Type = U.self) async throws -> U { public func single_request<U: Decodable>(_ request: String, params: String? = nil, type: U.Type = U.self) async throws -> U {
let req = BurrowSimpleRequest( let req = BurrowSimpleRequest(
id: generator.next(upperBound: UInt.max), id: generator.next(upperBound: UInt.max),
command: request command: request,
params:params
) )
return try await send(req) return try await send(req)
} }

125
Cargo.lock generated
View file

@ -353,10 +353,12 @@ dependencies = [
"rand_core", "rand_core",
"ring", "ring",
"rusqlite", "rusqlite",
"rust-ini",
"schemars", "schemars",
"serde", "serde",
"serde_json", "serde_json",
"tokio", "tokio",
"toml",
"tracing", "tracing",
"tracing-journald", "tracing-journald",
"tracing-log 0.1.4", "tracing-log 0.1.4",
@ -585,6 +587,26 @@ dependencies = [
"tracing-subscriber", "tracing-subscriber",
] ]
[[package]]
name = "const-random"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
dependencies = [
"const-random-macro",
]
[[package]]
name = "const-random-macro"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
"getrandom",
"once_cell",
"tiny-keccak",
]
[[package]] [[package]]
name = "constant_time_eq" name = "constant_time_eq"
version = "0.1.5" version = "0.1.5"
@ -640,6 +662,12 @@ version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]] [[package]]
name = "crypto-common" name = "crypto-common"
version = "0.1.6" version = "0.1.6"
@ -698,6 +726,15 @@ dependencies = [
"subtle", "subtle",
] ]
[[package]]
name = "dlv-list"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "dyn-clone" name = "dyn-clone"
version = "1.0.16" version = "1.0.16"
@ -1600,6 +1637,16 @@ dependencies = [
"vcpkg", "vcpkg",
] ]
[[package]]
name = "ordered-multimap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [
"dlv-list",
"hashbrown 0.14.3",
]
[[package]] [[package]]
name = "overload" name = "overload"
version = "0.1.1" version = "0.1.1"
@ -1946,6 +1993,17 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rust-ini"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d625ed57d8f49af6cfa514c42e1a71fadcff60eb0b1c517ff82fe41aa025b41"
dependencies = [
"cfg-if",
"ordered-multimap",
"trim-in-place",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -2102,6 +2160,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2337,6 +2404,15 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.6.0" version = "1.6.0"
@ -2426,6 +2502,40 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "toml"
version = "0.8.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3328d4f68a705b2a4498da1d580585d39a6510f98318a2cec3018a7ec61ddef"
dependencies = [
"indexmap 2.1.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]] [[package]]
name = "tonic" name = "tonic"
version = "0.10.2" version = "0.10.2"
@ -2583,6 +2693,12 @@ dependencies = [
"tracing-log 0.2.0", "tracing-log 0.2.0",
] ]
[[package]]
name = "trim-in-place"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.5" version = "0.2.5"
@ -2981,6 +3097,15 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winnow"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c52e9c97a68071b23e836c9380edae937f17b9c4667bd021973efc689f618d"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.50.0" version = "0.50.0"

View file

@ -24,7 +24,7 @@ clap = { version = "4.4", features = ["derive"] }
tracing = "0.1" tracing = "0.1"
tracing-log = "0.1" tracing-log = "0.1"
tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" } tracing-oslog = { git = "https://github.com/Stormshield-robinc/tracing-oslog" }
tracing-subscriber = { version = "0.3" , features = ["std", "env-filter"] } tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
log = "0.4" log = "0.4"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
@ -50,6 +50,8 @@ futures = "0.3.28"
once_cell = "1.19" once_cell = "1.19"
console-subscriber = { version = "0.2.0", optional = true } console-subscriber = { version = "0.2.0", optional = true }
console = "0.15.8" console = "0.15.8"
toml = "0.8.12"
rust-ini = "0.21.0"
[dependencies.rusqlite] [dependencies.rusqlite]
version = "0.31.0" version = "0.31.0"

View file

@ -17,7 +17,7 @@ use crate::{
ServerConfig, ServerConfig,
ServerInfo, ServerInfo,
}, },
database::{get_connection, load_interface}, database::{dump_interface, get_connection, load_interface},
wireguard::{Config, Interface}, wireguard::{Config, Interface},
}; };
@ -116,6 +116,12 @@ impl DaemonInstance {
.await?; .await?;
Ok(DaemonResponseData::None) Ok(DaemonResponseData::None)
} }
DaemonCommand::AddConfig(cfig_raw) => {
let conn = get_connection(self.db_path.as_deref())?;
let cfig = Config::from_content_fmt(&cfig_raw.content, &cfig_raw.fmt)?;
let _if_id = dump_interface(&conn, &cfig, cfig_raw.interface_id)?;
Ok(DaemonResponseData::None)
}
} }
} }

View file

@ -3,13 +3,14 @@ use serde::{Deserialize, Serialize};
use tun::TunOptions; use tun::TunOptions;
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(tag="method", content="params")] #[serde(tag = "method", content = "params")]
pub enum DaemonCommand { pub enum DaemonCommand {
Start(DaemonStartOptions), Start(DaemonStartOptions),
ServerInfo, ServerInfo,
ServerConfig, ServerConfig,
Stop, Stop,
ReloadConfig(String), ReloadConfig(String),
AddConfig(AddConfigOptions),
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)] #[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
@ -17,6 +18,13 @@ pub struct DaemonStartOptions {
pub tun: TunOptions, pub tun: TunOptions,
} }
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct AddConfigOptions {
pub content: String,
pub fmt: String,
pub interface_id: Option<i64>,
}
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct DaemonRequest { pub struct DaemonRequest {
pub id: u64, pub id: u64,

View file

@ -92,17 +92,36 @@ pub fn load_interface(conn: &Connection, interface_id: &str) -> Result<Config> {
Ok(Config { interface: iface, peers }) Ok(Config { interface: iface, peers })
} }
pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> { pub fn dump_interface(
let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?; conn: &Connection,
let cif = &config.interface; config: &Config,
stmt.execute(params![ interface_id: Option<i64>,
cif.private_key, ) -> Result<i64> {
to_lst(&cif.dns), let interface_id = if let Some(id) = interface_id {
to_lst(&cif.address), let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu, id) VALUES (?, ?, ?, ?, ?, ?)")?;
cif.listen_port, let cif = &config.interface;
cif.mtu stmt.execute(params![
])?; cif.private_key,
let interface_id = conn.last_insert_rowid(); to_lst(&cif.dns),
to_lst(&cif.address),
cif.listen_port,
cif.mtu,
id
])?;
id
} else {
let mut stmt = conn.prepare("INSERT INTO wg_interface (private_key, dns, address, listen_port, mtu) VALUES (?, ?, ?, ?, ?)")?;
let cif = &config.interface;
stmt.execute(params![
cif.private_key,
to_lst(&cif.dns),
to_lst(&cif.address),
cif.listen_port,
cif.mtu
])?;
conn.last_insert_rowid()
};
let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?; let mut stmt = conn.prepare("INSERT INTO wg_peer (interface_id, public_key, preshared_key, allowed_ips, endpoint) VALUES (?, ?, ?, ?, ?)")?;
for peer in &config.peers { for peer in &config.peers {
stmt.execute(params![ stmt.execute(params![
@ -113,7 +132,7 @@ pub fn dump_interface(conn: &Connection, config: &Config) -> Result<()> {
&peer.endpoint &peer.endpoint
])?; ])?;
} }
Ok(()) Ok(interface_id)
} }
pub fn get_connection(path: Option<&Path>) -> Result<Connection> { pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
@ -121,7 +140,7 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
if !p.exists() { if !p.exists() {
let conn = Connection::open(p)?; let conn = Connection::open(p)?;
initialize_tables(&conn)?; initialize_tables(&conn)?;
dump_interface(&conn, &Config::default())?; dump_interface(&conn, &Config::default(), None)?;
return Ok(conn); return Ok(conn);
} }
Ok(Connection::open(p)?) Ok(Connection::open(p)?)
@ -129,8 +148,6 @@ pub fn get_connection(path: Option<&Path>) -> Result<Connection> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::path::Path;
use super::*; use super::*;
#[test] #[test]
@ -138,7 +155,7 @@ mod tests {
let conn = Connection::open_in_memory().unwrap(); let conn = Connection::open_in_memory().unwrap();
initialize_tables(&conn).unwrap(); initialize_tables(&conn).unwrap();
let config = Config::default(); let config = Config::default();
dump_interface(&conn, &config).unwrap(); dump_interface(&conn, &config, None).unwrap();
let loaded = load_interface(&conn, "1").unwrap(); let loaded = load_interface(&conn, "1").unwrap();
assert_eq!(config, loaded); assert_eq!(config, loaded);
} }

View file

@ -1,6 +1,10 @@
use std::{borrow::Cow, path::PathBuf};
use anyhow::Result; use anyhow::Result;
use clap::{Args, Parser, Subcommand}; use clap::{Args, Parser, Subcommand};
use crate::daemon::rpc::request::AddConfigOptions;
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
mod daemon; mod daemon;
pub(crate) mod tracing; pub(crate) mod tracing;
@ -47,6 +51,16 @@ enum Commands {
ServerConfig, ServerConfig,
/// Reload Config /// Reload Config
ReloadConfig(ReloadConfigArgs), ReloadConfig(ReloadConfigArgs),
/// Add Server Config
AddConfig(AddServerConfigArgs),
}
#[derive(Args)]
struct AddServerConfigArgs {
#[clap(short, long)]
path: PathBuf,
#[clap(short, long)]
interface_id: Option<i64>,
} }
#[derive(Args)] #[derive(Args)]
@ -59,7 +73,12 @@ struct ReloadConfigArgs {
struct StartArgs {} struct StartArgs {}
#[derive(Args)] #[derive(Args)]
struct DaemonArgs {} struct DaemonArgs {
#[clap(long, short)]
socket_path: Option<PathBuf>,
#[clap(long, short)]
db_path: Option<PathBuf>,
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_start() -> Result<()> { async fn try_start() -> Result<()> {
@ -132,6 +151,24 @@ async fn try_reloadconfig(interface_id: String) -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(any(target_os = "linux", target_vendor = "apple"))]
async fn try_add_server_config(path: &PathBuf, interface_id: Option<i64>) -> Result<()> {
let mut client = DaemonClient::new().await?;
let ext = path
.extension()
.map(|e| e.to_string_lossy())
.unwrap_or_else(|| Cow::Borrowed("toml"));
let content = std::fs::read_to_string(path)?;
let res = client
.send_command(DaemonCommand::AddConfig(AddConfigOptions {
content,
fmt: ext.to_string(),
interface_id,
}))
.await?;
Ok(())
}
#[cfg(any(target_os = "linux", target_vendor = "apple"))] #[cfg(any(target_os = "linux", target_vendor = "apple"))]
#[tokio::main(flavor = "current_thread")] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
@ -139,12 +176,20 @@ async fn main() -> Result<()> {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Commands::Start(..) => try_start().await?, Commands::Start(_) => try_start().await?,
Commands::Stop => try_stop().await?, Commands::Stop => try_stop().await?,
Commands::Daemon(_) => daemon::daemon_main(None, None, None).await?, Commands::Daemon(daemon_args) => {
daemon::daemon_main(
daemon_args.socket_path.as_ref().map(|p| p.as_path()),
daemon_args.db_path.as_ref().map(|p| p.as_path()),
None,
)
.await?
}
Commands::ServerInfo => try_serverinfo().await?, Commands::ServerInfo => try_serverinfo().await?,
Commands::ServerConfig => try_serverconfig().await?, Commands::ServerConfig => try_serverconfig().await?,
Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?, Commands::ReloadConfig(args) => try_reloadconfig(args.interface_id.clone()).await?,
Commands::AddConfig(args) => try_add_server_config(&args.path, args.interface_id).await?,
} }
Ok(()) Ok(())

View file

@ -3,9 +3,12 @@ use std::{net::ToSocketAddrs, str::FromStr};
use anyhow::{anyhow, Error, Result}; use anyhow::{anyhow, Error, Result};
use base64::{engine::general_purpose, Engine}; use base64::{engine::general_purpose, Engine};
use fehler::throws; use fehler::throws;
use ini::{Ini, Properties};
use ip_network::IpNetwork; use ip_network::IpNetwork;
use serde::{Deserialize, Serialize};
use x25519_dalek::{PublicKey, StaticSecret}; use x25519_dalek::{PublicKey, StaticSecret};
use super::inifield::IniField;
use crate::wireguard::{Interface as WgInterface, Peer as WgPeer}; use crate::wireguard::{Interface as WgInterface, Peer as WgPeer};
#[throws] #[throws]
@ -31,7 +34,7 @@ fn parse_public_key(string: &str) -> PublicKey {
/// A raw version of Peer Config that can be used later to reflect configuration files. /// A raw version of Peer Config that can be used later to reflect configuration files.
/// This should be later converted to a `WgPeer`. /// This should be later converted to a `WgPeer`.
/// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview /// Refers to https://github.com/pirate/wireguard-docs?tab=readme-ov-file#overview
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Peer { pub struct Peer {
pub public_key: String, pub public_key: String,
pub preshared_key: Option<String>, pub preshared_key: Option<String>,
@ -41,7 +44,7 @@ pub struct Peer {
pub name: Option<String>, pub name: Option<String>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Interface { pub struct Interface {
pub private_key: String, pub private_key: String,
pub address: Vec<String>, pub address: Vec<String>,
@ -50,8 +53,9 @@ pub struct Interface {
pub mtu: Option<u32>, pub mtu: Option<u32>,
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct Config { pub struct Config {
#[serde(rename = "Peer")]
pub peers: Vec<Peer>, pub peers: Vec<Peer>,
pub interface: Interface, // Support for multiple interfaces? pub interface: Interface, // Support for multiple interfaces?
} }
@ -113,3 +117,83 @@ impl Default for Config {
} }
} }
} }
fn props_get<T>(props: &Properties, key: &str) -> T
where
T: From<IniField>,
{
IniField::from(props.get(key)).into()
}
impl TryFrom<&Properties> for Interface {
type Error = anyhow::Error;
fn try_from(props: &Properties) -> Result<Self, Error> {
Ok(Self {
private_key: props_get(props, "PrivateKey"),
address: props_get(props, "Address"),
listen_port: props_get(props, "ListenPort"),
dns: props_get(props, "DNS"),
mtu: props_get(props, "MTU"),
})
}
}
impl TryFrom<&Properties> for Peer {
type Error = anyhow::Error;
fn try_from(props: &Properties) -> Result<Self, Error> {
Ok(Self {
public_key: props_get(props, "PublicKey"),
preshared_key: props_get(props, "PresharedKey"),
allowed_ips: props_get(props, "AllowedIPs"),
endpoint: props_get(props, "Endpoint"),
persistent_keepalive: props_get(props, "PersistentKeepalive"),
name: props_get(props, "Name"),
})
}
}
impl Config {
pub fn from_toml(toml: &str) -> Result<Self> {
toml::from_str(toml).map_err(Into::into)
}
pub fn from_ini(ini: &str) -> Result<Self> {
let ini = Ini::load_from_str(ini)?;
let interface = ini
.section(Some("Interface"))
.ok_or(anyhow!("Interface section not found"))?;
let peers = ini.section_all(Some("Peer"));
Ok(Self {
interface: Interface::try_from(interface)?,
peers: peers
.into_iter()
.map(|v| Peer::try_from(v))
.collect::<Result<Vec<Peer>>>()?,
})
}
pub fn from_content_fmt(content: &str, fmt: &str) -> Result<Self> {
match fmt {
"toml" => Self::from_toml(content),
"ini" | "conf" => Self::from_ini(content),
_ => Err(anyhow::anyhow!("Unsupported format: {}", fmt)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tst_config_toml() {
let cfig = Config::default();
let toml = toml::to_string(&cfig).unwrap();
println!("{}", &toml);
insta::assert_snapshot!(toml);
let cfig2: Config = toml::from_str(&toml).unwrap();
assert_eq!(cfig, cfig2);
}
}

View file

@ -0,0 +1,59 @@
use std::str::FromStr;
pub struct IniField(String);
impl FromStr for IniField {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string()))
}
}
impl From<IniField> for Vec<String> {
fn from(field: IniField) -> Self {
field.0.split(",").map(|s| s.to_string()).collect()
}
}
impl From<IniField> for u32 {
fn from(value: IniField) -> Self {
value.0.parse().unwrap()
}
}
impl From<IniField> for Option<u32> {
fn from(value: IniField) -> Self {
Some(value.0.parse().unwrap())
}
}
impl From<IniField> for String {
fn from(value: IniField) -> Self {
value.0
}
}
impl From<IniField> for Option<String> {
fn from(value: IniField) -> Self {
Some(value.0)
}
}
impl<T> From<Option<T>> for IniField
where
T: ToString,
{
fn from(value: Option<T>) -> Self {
match value {
Some(v) => Self(v.to_string()),
None => Self("".to_string()),
}
}
}
impl IniField {
fn new(value: &str) -> Self {
Self(value.to_string())
}
}

View file

@ -1,5 +1,6 @@
pub mod config; pub mod config;
mod iface; mod iface;
mod inifield;
mod noise; mod noise;
mod pcb; mod pcb;
mod peer; mod peer;

View file

@ -0,0 +1,16 @@
---
source: burrow/src/wireguard/config.rs
expression: toml
---
[[peers]]
public_key = "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM="
preshared_key = "ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698="
allowed_ips = ["8.8.8.8/32", "0.0.0.0/0"]
endpoint = "wg.burrow.rs:51820"
[interface]
private_key = "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8="
address = ["10.13.13.2/24"]
listen_port = 51820
dns = []