innernet_server/
initialize.rs

1use crate::{
2    db::{self, DatabaseCidr, DatabasePeer},
3    ConfigFile, Interface, Path, ServerConfig,
4};
5use anyhow::{anyhow, Error};
6use clap::Parser;
7use colored::Colorize;
8use dialoguer::{theme::ColorfulTheme, Input};
9use indoc::printdoc;
10use innernet_publicip::Preference;
11use innernet_shared::{
12    prompts, CidrContents, Endpoint, IpNetExt, PeerContents, PERSISTENT_KEEPALIVE_INTERVAL_SECS,
13};
14use ipnet::IpNet;
15use rusqlite::{params, Connection};
16use std::net::{IpAddr, SocketAddr};
17use wireguard_control::KeyPair;
18
19fn create_database<P: AsRef<Path>>(
20    database_path: P,
21) -> Result<Connection, Box<dyn std::error::Error>> {
22    let conn = Connection::open(&database_path)?;
23    conn.pragma_update(None, "foreign_keys", 1)?;
24    conn.execute(db::peer::CREATE_TABLE_SQL, params![])?;
25    conn.execute(db::association::CREATE_TABLE_SQL, params![])?;
26    conn.execute(db::cidr::CREATE_TABLE_SQL, params![])?;
27    conn.pragma_update(None, "user_version", db::CURRENT_VERSION)?;
28    log::debug!("set database version to db::CURRENT_VERSION");
29
30    Ok(conn)
31}
32
33#[derive(Debug, Default, Clone, PartialEq, Eq, Parser)]
34pub struct InitializeOpts {
35    /// The network name (ex: evilcorp)
36    #[clap(long)]
37    pub network_name: Option<Interface>,
38
39    /// The network CIDR (ex: 10.42.0.0/16)
40    #[clap(long)]
41    pub network_cidr: Option<IpNet>,
42
43    /// This server's external endpoint (ex: 100.100.100.100:51820)
44    #[clap(long, conflicts_with = "auto_external_endpoint")]
45    pub external_endpoint: Option<Endpoint>,
46
47    /// Auto-resolve external endpoint
48    #[clap(long = "auto-external-endpoint")]
49    pub auto_external_endpoint: bool,
50
51    /// Port to listen on (for the WireGuard interface)
52    #[clap(long)]
53    pub listen_port: Option<u16>,
54}
55
56struct DbInitData {
57    network_name: String,
58    network_cidr: IpNet,
59    server_cidr: IpNet,
60    our_ip: IpAddr,
61    public_key_base64: String,
62    endpoint: Endpoint,
63}
64
65fn populate_database(conn: &Connection, db_init_data: DbInitData) -> Result<(), Error> {
66    const SERVER_NAME: &str = "innernet-server";
67
68    let root_cidr = DatabaseCidr::create(
69        conn,
70        CidrContents {
71            name: db_init_data.network_name.clone(),
72            cidr: db_init_data.network_cidr,
73            parent: None,
74        },
75    )
76    .map_err(|_| anyhow!("failed to create root CIDR"))?;
77
78    let server_cidr = DatabaseCidr::create(
79        conn,
80        CidrContents {
81            name: SERVER_NAME.into(),
82            cidr: db_init_data.server_cidr,
83            parent: Some(root_cidr.id),
84        },
85    )
86    .map_err(|_| anyhow!("failed to create innernet-server CIDR"))?;
87
88    let _me = DatabasePeer::create(
89        conn,
90        PeerContents {
91            name: SERVER_NAME.parse().map_err(|e: &str| anyhow!(e))?,
92            ip: db_init_data.our_ip,
93            cidr_id: server_cidr.id,
94            public_key: db_init_data.public_key_base64,
95            endpoint: Some(db_init_data.endpoint),
96            is_admin: true,
97            is_disabled: false,
98            is_redeemed: true,
99            persistent_keepalive_interval: Some(PERSISTENT_KEEPALIVE_INTERVAL_SECS),
100            invite_expires: None,
101            candidates: vec![],
102        },
103    )
104    .map_err(|_| anyhow!("failed to create innernet peer."))?;
105
106    Ok(())
107}
108
109pub fn init_wizard(conf: &ServerConfig, opts: InitializeOpts) -> Result<(), Error> {
110    let theme = ColorfulTheme::default();
111
112    innernet_shared::ensure_dirs_exist(&[conf.config_dir(), conf.database_dir()]).map_err(
113        |_| {
114            anyhow!(
115                "Failed to create config and database directories {}",
116                "(are you not running as root?)".bold()
117            )
118        },
119    )?;
120    printdoc!(
121        "\nTime to setup your innernet network.
122
123        Your network name can be any hostname-valid string, i.e. \"evilcorp\", and
124        your network CIDR should be in the RFC1918 IPv4 (10/8, 172.16/12, or 192.168/16), 
125        or RFC4193 IPv6 (fd00::/8) ranges.
126
127        The external endpoint specified is a <host>:<port> string that is the address clients
128        will connect to. It's up to you to forward/open ports in your routers/firewalls
129        as needed.
130
131        For more usage instructions, see https://github.com/tonarino/innernet#usage
132        \n"
133    );
134
135    let name: Interface = if let Some(name) = opts.network_name {
136        name
137    } else {
138        Input::with_theme(&theme)
139            .with_prompt("Network name")
140            .interact()?
141    };
142
143    let root_cidr: IpNet = if let Some(cidr) = opts.network_cidr {
144        cidr
145    } else {
146        Input::with_theme(&theme)
147            .with_prompt("Network CIDR")
148            .with_initial_text("10.42.0.0/16")
149            .interact()?
150    };
151
152    let listen_port: u16 = if let Some(listen_port) = opts.listen_port {
153        listen_port
154    } else {
155        Input::with_theme(&theme)
156            .with_prompt("Listen port")
157            .default(51820)
158            .interact()
159            .map_err(|_| anyhow!("failed to get listen port."))?
160    };
161
162    log::info!("listen port: {}", listen_port);
163
164    let endpoint: Endpoint = if let Some(endpoint) = opts.external_endpoint {
165        endpoint
166    } else if opts.auto_external_endpoint {
167        let ip = innernet_publicip::get_any(Preference::Ipv4)
168            .ok_or_else(|| anyhow!("couldn't get external IP"))?;
169        SocketAddr::new(ip, listen_port).into()
170    } else {
171        let external_ip = prompts::ip_auto_detection_flow()?;
172        prompts::input_external_endpoint(external_ip, listen_port)?
173    };
174
175    let our_ip = root_cidr
176        .hosts()
177        .find(|ip| root_cidr.is_assignable(ip))
178        .unwrap();
179    let config_path = conf.config_path(&name);
180    let our_keypair = KeyPair::generate();
181
182    let config = ConfigFile {
183        private_key: our_keypair.private.to_base64(),
184        listen_port,
185        address: our_ip,
186        network_cidr_prefix: root_cidr.prefix_len(),
187    };
188    config.write_to_path(config_path)?;
189
190    let db_init_data = DbInitData {
191        network_name: name.to_string(),
192        network_cidr: root_cidr,
193        server_cidr: IpNet::new(our_ip, root_cidr.max_prefix_len())?,
194        our_ip,
195        public_key_base64: our_keypair.public.to_base64(),
196        endpoint,
197    };
198
199    // TODO(bschwind) - Clean up the config file and database
200    //                  if any errors occur in these init calls.
201
202    let database_path = conf.database_path(&name);
203    let conn = create_database(&database_path).map_err(|_| {
204        anyhow!(
205            "failed to create database {}",
206            "(are you not running as root?)".bold()
207        )
208    })?;
209    populate_database(&conn, db_init_data)?;
210
211    println!(
212        "{} Created database at {}\n",
213        "[*]".dimmed(),
214        database_path.to_string_lossy().bold()
215    );
216    printdoc!(
217        "
218        {star} Setup finished.
219
220            Network {interface} has been {created}, but it's not started yet!
221
222            Your new network starts with only one peer: this innernet server. Next,
223            you'll want to create additional CIDRs and peers using the commands:
224
225                {wg_manage_server} {add_cidr} {interface}, and
226                {wg_manage_server} {add_peer} {interface}
227            
228            See https://github.com/tonarino/innernet for more detailed instruction
229            on designing your network.
230        
231            When you're ready to start the network, you can auto-start the server:
232            
233                {systemctl_enable}{interface}
234
235    ",
236        star = "[*]".dimmed(),
237        interface = name.to_string().yellow(),
238        created = "created".green(),
239        wg_manage_server = "innernet-server".yellow(),
240        add_cidr = "add-cidr".yellow(),
241        add_peer = "add-peer".yellow(),
242        systemctl_enable = "systemctl enable --now innernet-server@".yellow(),
243    );
244
245    Ok(())
246}